// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XX                                                                           XX
XX                             emitriscv64.cpp                               XX
XX                                                                           XX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/

#include "jitpch.h"
#ifdef _MSC_VER
#pragma hdrstop
#endif

#if defined(TARGET_RISCV64)

/*****************************************************************************/
/*****************************************************************************/

#include "instr.h"
#include "emit.h"
#include "codegen.h"

/*****************************************************************************/

const instruction emitJumpKindInstructions[] = {
    INS_nop,

#define JMP_SMALL(en, rev, ins) INS_##ins,
#include "emitjmps.h"
};

const emitJumpKind emitReverseJumpKinds[] = {
    EJ_NONE,

#define JMP_SMALL(en, rev, ins) EJ_##rev,
#include "emitjmps.h"
};

/*****************************************************************************
 * Look up the instruction for a jump kind
 */

/*static*/ instruction emitter::emitJumpKindToIns(emitJumpKind jumpKind)
{
    assert((unsigned)jumpKind < ArrLen(emitJumpKindInstructions));
    return emitJumpKindInstructions[jumpKind];
}

/*****************************************************************************
 * Reverse the conditional jump
 */

/*static*/ emitJumpKind emitter::emitReverseJumpKind(emitJumpKind jumpKind)
{
    assert(jumpKind < EJ_COUNT);
    return emitReverseJumpKinds[jumpKind];
}

/*****************************************************************************
 *
 *  Return the allocated size (in bytes) of the given instruction descriptor.
 */

size_t emitter::emitSizeOfInsDsc(instrDesc* id) const
{
    if (emitIsSmallInsDsc(id))
        return SMALL_IDSC_SIZE;

    insOpts insOp = id->idInsOpt();

    switch (insOp)
    {
        case INS_OPTS_JUMP:
            return sizeof(instrDescJmp);

        case INS_OPTS_C:
            if (id->idIsLargeCall())
            {
                /* Must be a "fat" call descriptor */
                return sizeof(instrDescCGCA);
            }
            else
            {
                assert(!id->idIsLargeDsp());
                assert(!id->idIsLargeCns());
                return sizeof(instrDesc);
            }

        case INS_OPTS_RC:
        case INS_OPTS_RL:
        case INS_OPTS_RELOC:
        case INS_OPTS_NONE:
            return sizeof(instrDesc);
        case INS_OPTS_I:
            return sizeof(instrDescLoadImm);
        default:
            NO_WAY("unexpected instruction descriptor format");
            break;
    }
}

bool emitter::emitInsWritesToLclVarStackLoc(instrDesc* id)
{
    if (!id->idIsLclVar())
        return false;

    instruction ins = id->idIns();

    // This list is related to the list of instructions used to store local vars in emitIns_S_R().
    // We don't accept writing to float local vars.

    switch (ins)
    {
        case INS_sd:
        case INS_sw:
        case INS_sb:
        case INS_sh:
            return true;

        default:
            return false;
    }
}

#define LD 1
#define ST 2

// clang-format off
/*static*/ const BYTE CodeGenInterface::instInfo[] =
{
    #define INST(id, nm, info, e1) info,
    #include "instrs.h"
};
// clang-format on

emitter::MajorOpcode emitter::GetMajorOpcode(code_t code)
{
    if (Is32BitInstruction((WORD)code))
    {
        code_t opcode = (code >> 2) & 0b11111;
        return (MajorOpcode)opcode;
    }
    else
    {
        code_t op     = code & 0b11;
        code_t funct3 = (code >> 13) & 0b111;
        code_t idx    = 32 + (op << 3) + funct3;
        return (MajorOpcode)idx;
    }
}

inline bool emitter::emitInsMayWriteToGCReg(instruction ins)
{
    assert(ins != INS_invalid);
    if (ins == INS_nop || ins == INS_j) // pseudos with 'zero' destination register
        return false;

    if (ins == INS_lea)
        return true;

    code_t code = emitInsCode(ins);
    switch (GetMajorOpcode(code))
    {
        // Opcodes with no destination register
        case MajorOpcode::Store:
        case MajorOpcode::StoreFp:
        case MajorOpcode::MiscMem:
        case MajorOpcode::Branch:
        // Opcodes with a floating-point destination register
        case MajorOpcode::LoadFp:
        case MajorOpcode::MAdd:
        case MajorOpcode::MSub:
        case MajorOpcode::NmSub:
        case MajorOpcode::NmAdd:
            return false;

        case MajorOpcode::System:
        {
            code_t funct3 = (code >> 12) & 0b111;
            return (funct3 != 0); // CSR read/writes
        }

        case MajorOpcode::OpFp:
        {
            // Lowest 2 bits of funct7 distinguish single, double, half, and quad floats; we don't care
            code_t funct7 = code >> (25 + 2);
            switch (funct7)
            {
                case 0b10100: // comparisons: feq, flt, fle
                case 0b11100: // fmv to integer or fclass
                case 0b11000: // fcvt to integer
                    return true;
                default:
                    return false;
            }
        }

        case MajorOpcode::Custom0:
        case MajorOpcode::Custom1:
        case MajorOpcode::Custom2Rv128:
        case MajorOpcode::Custom3Rv128:
        case MajorOpcode::OpV:
        case MajorOpcode::OpVe:
        case MajorOpcode::Reserved:
            assert(!"unsupported major opcode");
            FALLTHROUGH;

        default: // all other opcodes write to a general purpose destination register
            return true;
    }
}

//------------------------------------------------------------------------
// emitInsLoad: Returns true if the instruction is some kind of load instruction.
//
bool emitter::emitInsIsLoad(instruction ins)
{
    // We have pseudo ins like lea which are not included in emitInsLdStTab.
    if (ins < ArrLen(CodeGenInterface::instInfo))
        return (CodeGenInterface::instInfo[ins] & LD) != 0;
    else
        return false;
}

//------------------------------------------------------------------------
// emitInsIsStore: Returns true if the instruction is some kind of store instruction.
//
bool emitter::emitInsIsStore(instruction ins)
{
    // We have pseudo ins like lea which are not included in emitInsLdStTab.
    if (ins < ArrLen(CodeGenInterface::instInfo))
        return (CodeGenInterface::instInfo[ins] & ST) != 0;
    else
        return false;
}

//-------------------------------------------------------------------------
// emitInsIsLoadOrStore: Returns true if the instruction is some kind of load/store instruction.
//
bool emitter::emitInsIsLoadOrStore(instruction ins)
{
    // We have pseudo ins like lea which are not included in emitInsLdStTab.
    if (ins < ArrLen(CodeGenInterface::instInfo))
        return (CodeGenInterface::instInfo[ins] & (LD | ST)) != 0;
    else
        return false;
}

/*****************************************************************************
 *
 *  Returns the specific encoding of the given CPU instruction.
 */

inline emitter::code_t emitter::emitInsCode(instruction ins /*, insFormat fmt*/)
{
    code_t code = BAD_CODE;

    // clang-format off
    const static code_t insCode[] =
    {
        #define INST(id, nm, info, e1) e1,
        #include "instrs.h"
    };
    // clang-format on

    assert(ins < ArrLen(insCode));
    code = insCode[ins];

    assert(code != BAD_CODE);

    return code;
}

/****************************************************************************
 *
 *  Add an instruction with no operands.
 */

void emitter::emitIns(instruction ins)
{
    instrDesc* id = emitNewInstr(EA_8BYTE);

    id->idIns(ins);
    id->idAddr()->iiaSetInstrEncode(emitInsCode(ins));
    id->idCodeSize(4);

    appendToCurIG(id);
}

/*****************************************************************************
 *  emitter::emitIns_S_R(), emitter::emitIns_S_R_R() and emitter::emitIns_R_S():
 *
 *  Add an Load/Store instruction(s): base+offset and base-addr-computing if needed.
 *  For referencing a stack-based local variable and a register
 *
 */
void emitter::emitIns_S_R(instruction ins, emitAttr attr, regNumber reg1, int varx, int offs)
{
    emitIns_S_R_R(ins, attr, reg1, REG_NA, varx, offs);
}

void emitter::emitIns_S_R_R(instruction ins, emitAttr attr, regNumber reg1, regNumber tmpReg, int varx, int offs)
{
    ssize_t imm;

    assert(tmpReg != codeGen->rsGetRsvdReg());
    assert(reg1 != codeGen->rsGetRsvdReg());

    emitAttr size = EA_SIZE(attr);

#ifdef DEBUG
    switch (ins)
    {
        case INS_sd:
        case INS_sw:
        case INS_sh:
        case INS_sb:
        case INS_fsd:
        case INS_fsw:
            break;

        default:
            NYI_RISCV64("illegal ins within emitIns_S_R_R!");
            return;

    } // end switch (ins)
#endif

    /* Figure out the variable's frame position */
    int  base;
    bool FPbased;

    base = emitComp->lvaFrameAddress(varx, &FPbased);

    regNumber regBase = FPbased ? REG_FPBASE : REG_SPBASE;
    regNumber reg2;

    if (tmpReg == REG_NA)
    {
        reg2 = regBase;
        imm  = base + offs;
    }
    else
    {
        reg2 = tmpReg;
        imm  = offs;
    }

    assert(reg2 != REG_NA && reg2 != codeGen->rsGetRsvdReg());

    if (!isValidSimm12(imm))
    {
        // If immediate does not fit to store immediate 12 bits, construct necessary value in rsRsvdReg()
        // and keep tmpReg hint value unchanged.
        assert(isValidSimm20((imm + 0x800) >> 12));

        emitIns_R_I(INS_lui, EA_PTRSIZE, codeGen->rsGetRsvdReg(), (imm + 0x800) >> 12);
        emitIns_R_R_R(INS_add, EA_PTRSIZE, codeGen->rsGetRsvdReg(), codeGen->rsGetRsvdReg(), reg2);

        imm  = imm & 0xfff;
        reg2 = codeGen->rsGetRsvdReg();
    }

    instrDesc* id = emitNewInstr(attr);

    id->idReg1(reg1);

    id->idReg2(reg2);

    id->idIns(ins);

    assert(isGeneralRegister(reg2));
    code_t code = emitInsCode(ins);
    code |= (code_t)(reg1 & 0x1f) << 20;
    code |= (code_t)reg2 << 15;
    code |= (((imm >> 5) & 0x7f) << 25) | ((imm & 0x1f) << 7);

    id->idAddr()->iiaSetInstrEncode(code);
    id->idAddr()->iiaLclVar.initLclVarAddr(varx, offs);
    id->idSetIsLclVar();
    id->idCodeSize(4);

    appendToCurIG(id);
}

/*
 *  Special notes for `offs`, please see the comment for `emitter::emitIns_S_R`.
 */
void emitter::emitIns_R_S(instruction ins, emitAttr attr, regNumber reg1, int varx, int offs)
{
    ssize_t imm;

    emitAttr size = EA_SIZE(attr);

#ifdef DEBUG
    switch (ins)
    {
        case INS_lb:
        case INS_lbu:

        case INS_lh:
        case INS_lhu:

        case INS_lw:
        case INS_lwu:
        case INS_flw:

        case INS_ld:
        case INS_fld:

            break;

        case INS_lea:
            assert(size == EA_8BYTE);
            break;

        default:
            NYI_RISCV64("illegal ins within emitIns_R_S!");
            return;

    } // end switch (ins)
#endif

    /* Figure out the variable's frame position */
    int  base;
    bool FPbased;

    base = emitComp->lvaFrameAddress(varx, &FPbased);
    imm  = offs < 0 ? -offs - 8 : base + offs;

    regNumber reg2 = FPbased ? REG_FPBASE : REG_SPBASE;
    assert(offs >= 0);
    offs = offs < 0 ? -offs - 8 : offs;

    reg1 = (regNumber)((char)reg1 & 0x1f);
    code_t code;
    if ((-2048 <= imm) && (imm < 2048))
    {
        if (ins == INS_lea)
        {
            ins = INS_addi;
        }
        code = emitInsCode(ins);
        code |= (code_t)reg1 << 7;
        code |= (code_t)reg2 << 15;
        code |= (imm & 0xfff) << 20;
    }
    else
    {
        if (ins == INS_lea)
        {
            assert(isValidSimm20((imm + 0x800) >> 12));
            emitIns_R_I(INS_lui, EA_PTRSIZE, codeGen->rsGetRsvdReg(), (imm + 0x800) >> 12);
            ssize_t imm2 = imm & 0xfff;
            emitIns_R_R_I(INS_addi, EA_PTRSIZE, codeGen->rsGetRsvdReg(), codeGen->rsGetRsvdReg(), imm2);

            ins  = INS_add;
            code = emitInsCode(ins);
            code |= (code_t)reg1 << 7;
            code |= (code_t)reg2 << 15;
            code |= (code_t)codeGen->rsGetRsvdReg() << 20;
        }
        else
        {
            assert(isValidSimm20((imm + 0x800) >> 12));
            emitIns_R_I(INS_lui, EA_PTRSIZE, codeGen->rsGetRsvdReg(), (imm + 0x800) >> 12);

            emitIns_R_R_R(INS_add, EA_PTRSIZE, codeGen->rsGetRsvdReg(), codeGen->rsGetRsvdReg(), reg2);

            ssize_t imm2 = imm & 0xfff;
            code         = emitInsCode(ins);
            code |= (code_t)reg1 << 7;
            code |= (code_t)codeGen->rsGetRsvdReg() << 15;
            code |= (code_t)(imm2 & 0xfff) << 20;
        }
    }

    instrDesc* id = emitNewInstr(attr);

    id->idReg1(reg1);

    id->idIns(ins);

    id->idAddr()->iiaSetInstrEncode(code);
    id->idAddr()->iiaLclVar.initLclVarAddr(varx, offs);
    id->idSetIsLclVar();
    id->idCodeSize(4);

    appendToCurIG(id);
}

/*****************************************************************************
 *
 *  Add an instruction with a single immediate value.
 */

void emitter::emitIns_I(instruction ins, emitAttr attr, ssize_t imm)
{
    code_t code = emitInsCode(ins);

    switch (ins)
    {
        case INS_fence:
            code |= ((imm & 0xff) << 20);
            break;
        case INS_j:
            assert(imm >= -1048576 && imm < 1048576);
            code |= ((imm >> 12) & 0xff) << 12;
            code |= ((imm >> 11) & 0x1) << 20;
            code |= ((imm >> 1) & 0x3ff) << 21;
            code |= ((imm >> 20) & 0x1) << 31;
            break;
        default:
            NO_WAY("illegal ins within emitIns_I!");
    }

    instrDesc* id = emitNewInstr(attr);

    id->idIns(ins);
    id->idAddr()->iiaSetInstrEncode(code);
    id->idCodeSize(4);

    appendToCurIG(id);
}

void emitter::emitIns_I_I(instruction ins, emitAttr attr, ssize_t cc, ssize_t offs)
{
    NYI_RISCV64("emitIns_I_I-----unimplemented/unused on RISCV64 yet----");
}

/*****************************************************************************
 *
 *  Add an instruction referencing a register and a constant.
 */

void emitter::emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, ssize_t imm, insOpts opt /* = INS_OPTS_NONE */)
{
    code_t code = emitInsCode(ins);

    switch (ins)
    {
        case INS_lui:
        case INS_auipc:
            assert(reg != REG_R0);
            assert(isGeneralRegister(reg));
            assert(isValidSimm20(imm));

            code |= reg << 7;
            code |= (imm & 0xfffff) << 12;
            break;
        case INS_jal:
            assert(isGeneralRegisterOrR0(reg));
            assert(isValidSimm21(imm));

            code |= reg << 7;
            code |= ((imm >> 12) & 0xff) << 12;
            code |= ((imm >> 11) & 0x1) << 20;
            code |= ((imm >> 1) & 0x3ff) << 21;
            code |= ((imm >> 20) & 0x1) << 31;
            break;
        default:
            NO_WAY("illegal ins within emitIns_R_I!");
            break;
    } // end switch (ins)

    instrDesc* id = emitNewInstr(attr);

    id->idIns(ins);
    id->idReg1(reg);
    id->idAddr()->iiaSetInstrEncode(code);
    id->idCodeSize(4);

    appendToCurIG(id);
}

//------------------------------------------------------------------------
// emitIns_Mov: Emits a move instruction
//
// Arguments:
//    ins       -- The instruction being emitted
//    attr      -- The emit attribute
//    dstReg    -- The destination register
//    srcReg    -- The source register
//    canSkip   -- true if the move can be elided when dstReg == srcReg, otherwise false
//    insOpts   -- The instruction options
//
void emitter::emitIns_Mov(
    instruction ins, emitAttr attr, regNumber dstReg, regNumber srcReg, bool canSkip, insOpts opt /* = INS_OPTS_NONE */)
{
    if (!canSkip || (dstReg != srcReg))
    {
        if ((EA_4BYTE == attr) && (INS_mov == ins))
        {
            assert(isGeneralRegisterOrR0(srcReg));
            assert(isGeneralRegisterOrR0(dstReg));
            emitIns_R_R(INS_sext_w, attr, dstReg, srcReg);
        }
        else if (INS_fsgnj_s == ins || INS_fsgnj_d == ins)
        {
            assert(isFloatReg(srcReg));
            assert(isFloatReg(dstReg));
            emitIns_R_R_R(ins, attr, dstReg, srcReg, srcReg);
        }
        else if (genIsValidFloatReg(srcReg) || genIsValidFloatReg(dstReg))
        {
            emitIns_R_R(ins, attr, dstReg, srcReg);
        }
        else
        {
            assert(isGeneralRegisterOrR0(srcReg));
            assert(isGeneralRegisterOrR0(dstReg));
            emitIns_R_R(INS_mov, attr, dstReg, srcReg);
        }
    }
}

void emitter::emitIns_Mov(emitAttr attr, regNumber dstReg, regNumber srcReg, bool canSkip)
{
    if (!canSkip || dstReg != srcReg)
    {
        assert(attr == EA_4BYTE || attr == EA_PTRSIZE);
        if (isGeneralRegisterOrR0(dstReg) && isGeneralRegisterOrR0(srcReg))
        {
            emitIns_R_R(attr == EA_4BYTE ? INS_sext_w : INS_mov, attr, dstReg, srcReg);
        }
        else if (isGeneralRegisterOrR0(dstReg) && genIsValidFloatReg(srcReg))
        {
            emitIns_R_R(attr == EA_4BYTE ? INS_fmv_x_w : INS_fmv_x_d, attr, dstReg, srcReg);
        }
        else if (genIsValidFloatReg(dstReg) && isGeneralRegisterOrR0(srcReg))
        {
            emitIns_R_R(attr == EA_4BYTE ? INS_fmv_w_x : INS_fmv_d_x, attr, dstReg, srcReg);
        }
        else if (genIsValidFloatReg(dstReg) && genIsValidFloatReg(srcReg))
        {
            emitIns_R_R_R(attr == EA_4BYTE ? INS_fsgnj_s : INS_fsgnj_d, attr, dstReg, srcReg, srcReg);
        }
        else
        {
            assert(!"Invalid registers in emitIns_Mov()\n");
        }
    }
}

/*****************************************************************************
 *
 *  Add an instruction referencing two registers
 */

void emitter::emitIns_R_R(
    instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, insOpts opt /* = INS_OPTS_NONE */)
{
    code_t code = emitInsCode(ins);

    if (INS_mov == ins || INS_sext_w == ins || INS_not == ins || (INS_clz <= ins && ins <= INS_rev8))
    {
        assert(isGeneralRegisterOrR0(reg1));
        assert(isGeneralRegisterOrR0(reg2));
        code |= reg1 << 7;
        code |= reg2 << 15;
    }
    else if (INS_fmv_x_d == ins || INS_fmv_x_w == ins || INS_fclass_s == ins || INS_fclass_d == ins)
    {
        assert(isGeneralRegisterOrR0(reg1));
        assert(isFloatReg(reg2));
        code |= reg1 << 7;
        code |= (reg2 & 0x1f) << 15;
    }
    else if (INS_fcvt_w_s == ins || INS_fcvt_wu_s == ins || INS_fcvt_w_d == ins || INS_fcvt_wu_d == ins ||
             INS_fcvt_l_s == ins || INS_fcvt_lu_s == ins || INS_fcvt_l_d == ins || INS_fcvt_lu_d == ins)
    {
        assert(isGeneralRegisterOrR0(reg1));
        assert(isFloatReg(reg2));
        code |= reg1 << 7;
        code |= (reg2 & 0x1f) << 15;
        code |= 0x1 << 12;
    }
    else if (INS_fmv_w_x == ins || INS_fmv_d_x == ins)
    {
        assert(isFloatReg(reg1));
        assert(isGeneralRegisterOrR0(reg2));
        code |= (reg1 & 0x1f) << 7;
        code |= reg2 << 15;
    }
    else if (INS_fcvt_s_w == ins || INS_fcvt_s_wu == ins || INS_fcvt_d_w == ins || INS_fcvt_d_wu == ins ||
             INS_fcvt_s_l == ins || INS_fcvt_s_lu == ins || INS_fcvt_d_l == ins || INS_fcvt_d_lu == ins)
    {
        assert(isFloatReg(reg1));
        assert(isGeneralRegisterOrR0(reg2));
        code |= (reg1 & 0x1f) << 7;
        code |= reg2 << 15;
        if (INS_fcvt_d_w != ins && INS_fcvt_d_wu != ins) // fcvt.d.w[u] always produces an exact result
            code |= 0x7 << 12;                           // round according to frm status register
    }
    else if (INS_fcvt_s_d == ins || INS_fcvt_d_s == ins || INS_fsqrt_s == ins || INS_fsqrt_d == ins)
    {
        assert(isFloatReg(reg1));
        assert(isFloatReg(reg2));
        code |= (reg1 & 0x1f) << 7;
        code |= (reg2 & 0x1f) << 15;
        if (INS_fcvt_d_s != ins) // fcvt.d.s never rounds
            code |= 0x7 << 12;   // round according to frm status register
    }
    else
    {
        NYI_RISCV64("illegal ins within emitIns_R_R!");
    }

    instrDesc* id = emitNewInstr(attr);

    id->idIns(ins);
    id->idReg1(reg1);
    id->idReg2(reg2);
    id->idAddr()->iiaSetInstrEncode(code);
    id->idCodeSize(4);

    appendToCurIG(id);
}

/*****************************************************************************
 *
 *  Add an instruction referencing two registers and a constant.
 */

void emitter::emitIns_R_R_I(
    instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, ssize_t imm, insOpts opt /* = INS_OPTS_NONE */)
{
    code_t     code = emitInsCode(ins);
    instrDesc* id   = emitNewInstr(attr);

    if ((INS_addi <= ins && INS_srai >= ins) || (INS_addiw <= ins && INS_sraiw >= ins) ||
        (INS_lb <= ins && INS_lhu >= ins) || INS_ld == ins || INS_lw == ins || INS_jalr == ins || INS_fld == ins ||
        INS_flw == ins || INS_slli_uw == ins || INS_rori == ins || INS_roriw == ins)
    {
        assert(isGeneralRegisterOrR0(reg2));
        code |= (reg1 & 0x1f) << 7; // rd
        code |= reg2 << 15;         // rs1
        code |= imm << 20;          // imm
    }
    else if (INS_sd == ins || INS_sw == ins || INS_sh == ins || INS_sb == ins || INS_fsw == ins || INS_fsd == ins)
    {
        assert(isGeneralRegister(reg2));
        code |= (reg1 & 0x1f) << 20;                               // rs2
        code |= reg2 << 15;                                        // rs1
        code |= (((imm >> 5) & 0x7f) << 25) | ((imm & 0x1f) << 7); // imm
    }
    else if (INS_beq <= ins && INS_bgeu >= ins)
    {
        assert(isGeneralRegister(reg1));
        assert(isGeneralRegister(reg2));
        assert(isValidSimm13(imm));
        assert(!(imm & 3));
        code |= reg1 << 15;
        code |= reg2 << 20;
        code |= ((imm >> 11) & 0x1) << 7;
        code |= ((imm >> 1) & 0xf) << 8;
        code |= ((imm >> 5) & 0x3f) << 25;
        code |= ((imm >> 12) & 0x1) << 31;
        // TODO-RISCV64: Move jump logic to emitIns_J
        // TODO-RISC64-RVC: Remove this once all branches uses emitIns_J
        id->idAddr()->iiaSetInstrCount(static_cast<int>(imm / sizeof(code_t)));
    }
    else if (ins == INS_csrrs || ins == INS_csrrw || ins == INS_csrrc)
    {
        assert(isGeneralRegisterOrR0(reg1));
        assert(isGeneralRegisterOrR0(reg2));
        assert(isValidUimm12(imm));
        code |= reg1 << 7;
        code |= reg2 << 15;
        code |= imm << 20;
    }
    else
    {
        NYI_RISCV64("illegal ins within emitIns_R_R_I!");
    }

    id->idIns(ins);
    id->idReg1(reg1);
    id->idReg2(reg2);
    id->idAddr()->iiaSetInstrEncode(code);
    id->idCodeSize(4);

    appendToCurIG(id);
}

/*****************************************************************************
 *
 *  Add an instruction referencing register and two constants.
 */

void emitter::emitIns_R_I_I(
    instruction ins, emitAttr attr, regNumber reg1, ssize_t imm1, ssize_t imm2, insOpts opt) /* = INS_OPTS_NONE */
{
    code_t code = emitInsCode(ins);

    if (INS_csrrwi <= ins && ins <= INS_csrrci)
    {
        assert(isGeneralRegisterOrR0(reg1));
        assert(isValidUimm5(imm1));
        assert(isValidUimm12(imm2));
        code |= reg1 << 7;
        code |= imm1 << 15;
        code |= imm2 << 20;
    }
    else
    {
        NYI_RISCV64("illegal ins within emitIns_R_I_I!");
    }
    instrDesc* id = emitNewInstr(attr);

    id->idIns(ins);
    id->idReg1(reg1);
    id->idAddr()->iiaSetInstrEncode(code);
    id->idCodeSize(4);

    appendToCurIG(id);
}

/*****************************************************************************
 *
 *  Add an instruction referencing three registers.
 */

void emitter::emitIns_R_R_R(
    instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, regNumber reg3, insOpts opt) /* = INS_OPTS_NONE */
{
    if (tryEmitCompressedIns_R_R_R(ins, attr, reg1, reg2, reg3, opt))
    {
        return;
    }

    code_t code = emitInsCode(ins);

    if ((INS_add <= ins && ins <= INS_and) || (INS_mul <= ins && ins <= INS_remuw) ||
        (INS_addw <= ins && ins <= INS_sraw) || (INS_fadd_s <= ins && ins <= INS_fmax_s) ||
        (INS_fadd_d <= ins && ins <= INS_fmax_d) || (INS_feq_s <= ins && ins <= INS_fle_s) ||
        (INS_feq_d <= ins && ins <= INS_fle_d) || (INS_lr_w <= ins && ins <= INS_amomaxu_d) ||
        (INS_sh1add <= ins && ins <= INS_sh3add_uw) || (INS_rol <= ins && ins <= INS_maxu))
    {
#ifdef DEBUG
        switch (ins)
        {
            case INS_add:
            case INS_sub:
            case INS_sll:
            case INS_slt:
            case INS_sltu:
            case INS_xor:
            case INS_srl:
            case INS_sra:
            case INS_or:
            case INS_and:

            case INS_addw:
            case INS_subw:
            case INS_sllw:
            case INS_srlw:
            case INS_sraw:

            case INS_mul:
            case INS_mulh:
            case INS_mulhsu:
            case INS_mulhu:
            case INS_div:
            case INS_divu:
            case INS_rem:
            case INS_remu:

            case INS_mulw:
            case INS_divw:
            case INS_divuw:
            case INS_remw:
            case INS_remuw:

            case INS_fadd_s:
            case INS_fsub_s:
            case INS_fmul_s:
            case INS_fdiv_s:
            case INS_fsgnj_s:
            case INS_fsgnjn_s:
            case INS_fsgnjx_s:
            case INS_fmin_s:
            case INS_fmax_s:

            case INS_feq_s:
            case INS_flt_s:
            case INS_fle_s:

            case INS_fadd_d:
            case INS_fsub_d:
            case INS_fmul_d:
            case INS_fdiv_d:
            case INS_fsgnj_d:
            case INS_fsgnjn_d:
            case INS_fsgnjx_d:
            case INS_fmin_d:
            case INS_fmax_d:

            case INS_feq_d:
            case INS_flt_d:
            case INS_fle_d:

            case INS_lr_w:
            case INS_lr_d:
            case INS_sc_w:
            case INS_sc_d:
            case INS_amoswap_w:
            case INS_amoswap_d:
            case INS_amoadd_w:
            case INS_amoadd_d:
            case INS_amoxor_w:
            case INS_amoxor_d:
            case INS_amoand_w:
            case INS_amoand_d:
            case INS_amoor_w:
            case INS_amoor_d:
            case INS_amomin_w:
            case INS_amomin_d:
            case INS_amomax_w:
            case INS_amomax_d:
            case INS_amominu_w:
            case INS_amominu_d:
            case INS_amomaxu_w:
            case INS_amomaxu_d:

            case INS_sh1add:
            case INS_sh2add:
            case INS_sh3add:
            case INS_add_uw:
            case INS_sh1add_uw:
            case INS_sh2add_uw:
            case INS_sh3add_uw:

            case INS_rol:
            case INS_rolw:
            case INS_ror:
            case INS_rorw:
            case INS_xnor:
            case INS_orn:
            case INS_andn:
            case INS_min:
            case INS_minu:
            case INS_max:
            case INS_maxu:
                break;
            default:
                NYI_RISCV64("illegal ins within emitIns_R_R_R!");
        }

#endif
        // Src/data register for load reserved should be empty
        assert((ins != INS_lr_w && ins != INS_lr_d) || reg3 == REG_R0);

        code |= ((reg1 & 0x1f) << 7);
        code |= ((reg2 & 0x1f) << 15);
        code |= ((reg3 & 0x1f) << 20);
        if ((INS_fadd_s <= ins && INS_fdiv_s >= ins) || (INS_fadd_d <= ins && INS_fdiv_d >= ins))
        {
            code |= 0x7 << 12;
        }
        else if (ins == INS_sc_w || ins == INS_sc_d)
        {
            code |= 0b10 << 25; // release ordering, it ends the lr-sc loop
        }
        else if ((ins == INS_lr_w || ins == INS_lr_d) || (INS_amoswap_w <= ins && ins <= INS_amomaxu_d))
        {
            // For now all atomics are seq. consistent as Interlocked.* APIs don't expose acquire/release ordering
            code |= 0b11 << 25;
        }
    }
    else
    {
        NYI_RISCV64("illegal ins within emitIns_R_R_R!");
    }

    instrDesc* id = emitNewInstr(attr);

    id->idIns(ins);
    id->idReg1(reg1);
    id->idReg2(reg2);
    id->idReg3(reg3);
    id->idAddr()->iiaSetInstrEncode(code);
    id->idCodeSize(4);

    appendToCurIG(id);
}

bool emitter::tryEmitCompressedIns_R_R_R(
    instruction ins, emitAttr attr, regNumber rd, regNumber rs1, regNumber rs2, insOpts opt)
{
    // TODO-RISCV64-RVC: Disable this early return once compresed instructions are allowed in prolog / epilog
    if (emitComp->compGeneratingProlog || emitComp->compGeneratingEpilog)
    {
        return false;
    }

    instruction compressedIns = tryGetCompressedIns_R_R_R(ins, attr, rd, rs1, rs2, opt);
    if (compressedIns == INS_none)
    {
        return false;
    }

    code_t code;
    switch (compressedIns)
    {
        case INS_c_mv:
            code = insEncodeCRTypeInstr(compressedIns, rd, rs2);
            break;
        case INS_c_add:
            code = insEncodeCRTypeInstr(compressedIns, rd, rs2);
            break;
        case INS_c_and:
        case INS_c_or:
        case INS_c_xor:
        case INS_c_sub:
        case INS_c_addw:
        case INS_c_subw:
        {
            unsigned rdRvc  = tryGetRvcRegisterNumber(rd);
            unsigned rs2Rvc = tryGetRvcRegisterNumber(rs2);
            assert((rdRvc != -1) && (rs2Rvc != -1));
            code = insEncodeCATypeInstr(compressedIns, rdRvc, rs2Rvc);
            break;
        }
        default:
            return false;
    };

    instrDesc* id = emitNewInstr(attr);

    id->idIns(ins);
    id->idReg1(rd);
    id->idReg2(rs1);
    id->idReg3(rs2);
    id->idAddr()->iiaSetInstrEncode(code);
    id->idCodeSize(2);

    appendToCurIG(id);

    return true;
}

instruction emitter::tryGetCompressedIns_R_R_R(
    instruction ins, emitAttr attr, regNumber rd, regNumber rs1, regNumber rs2, insOpts opt)
{
    switch (ins)
    {
        case INS_add:
        {
            if ((rs1 == REG_R0) && (rd != REG_R0) && (rs2 != REG_R0))
            {
                return INS_c_mv;
            }
            else if ((rd == rs1) && (rd != REG_R0) && (rs2 != REG_R0))
            {
                return INS_c_add;
            }
            break;
        }
        case INS_and:
        case INS_or:
        case INS_xor:
        case INS_sub:
        case INS_addw:
        case INS_subw:
        {
            unsigned rdRvc  = tryGetRvcRegisterNumber(rd);
            unsigned rs2Rvc = tryGetRvcRegisterNumber(rs2);
            if ((rd == rs1) && (rdRvc != -1) && (rs2Rvc != -1))
            {
                return getCompressedArithmeticIns(ins);
            }
            break;
        }
        default:
            break;
    };
    return INS_none;
}

unsigned emitter::tryGetRvcRegisterNumber(regNumber reg)
{
    switch (reg)
    {
        case REG_FP:
            return 0;
        case REG_S1:
            return 1;
        case REG_A0:
            return 2;
        case REG_A1:
            return 3;
        case REG_A2:
            return 4;
        case REG_A3:
            return 5;
        case REG_A4:
            return 6;
        case REG_A5:
            return 7;
        default:
            return -1;
    }
}

regNumber emitter::getRegNumberFromRvcReg(unsigned rvcReg)
{
    assert((rvcReg >> 3) == 0);
    switch (rvcReg)
    {
        case 0:
            return REG_FP;
        case 1:
            return REG_S1;
        case 2:
            return REG_A0;
        case 3:
            return REG_A1;
        case 4:
            return REG_A2;
        case 5:
            return REG_A3;
        case 6:
            return REG_A4;
        case 7:
            return REG_A5;
        default:
            unreached();
    }
}

instruction emitter::getCompressedArithmeticIns(instruction ins)
{
    assert((ins == INS_and) || (ins == INS_or) || (ins == INS_xor) || (ins == INS_sub) || (ins == INS_addw) ||
           (ins == INS_subw));
    switch (ins)
    {
        case INS_and:
            return INS_c_and;
        case INS_or:
            return INS_c_or;
        case INS_xor:
            return INS_c_xor;
        case INS_sub:
            return INS_c_sub;
        case INS_addw:
            return INS_c_addw;
        case INS_subw:
            return INS_c_subw;
        default:
            unreached();
    }
}

/*****************************************************************************
 *
 *  Add an instruction referencing three registers and a constant.
 */

void emitter::emitIns_R_R_R_I(instruction ins,
                              emitAttr    attr,
                              regNumber   reg1,
                              regNumber   reg2,
                              regNumber   reg3,
                              ssize_t     imm,
                              insOpts     opt /* = INS_OPTS_NONE */,
                              emitAttr    attrReg2 /* = EA_UNKNOWN */)
{
    NYI_RISCV64("emitIns_R_R_R_I-----unimplemented/unused on RISCV64 yet----");
}

/*****************************************************************************
 *
 *  Add an instruction referencing two registers and two constants.
 */

void emitter::emitIns_R_R_I_I(
    instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, int imm1, int imm2, insOpts opt)
{
    NYI_RISCV64("emitIns_R_R_I_I-----unimplemented/unused on RISCV64 yet----");
}

/*****************************************************************************
 *
 *  Add an instruction referencing four registers.
 */

void emitter::emitIns_R_R_R_R(
    instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, regNumber reg3, regNumber reg4)
{
    NYI_RISCV64("emitIns_R_R_R_R-----unimplemented/unused on RISCV64 yet----");
}

/*****************************************************************************
 *
 *  Add an instruction with a register + static member operands.
 *  Constant is stored into JIT data which is adjacent to code.
 *
 */
void emitter::emitIns_R_C(
    instruction ins, emitAttr attr, regNumber destReg, regNumber addrReg, CORINFO_FIELD_HANDLE fldHnd)
{
    instrDesc* id = emitNewInstr(attr);
    id->idIns(ins);
    assert(destReg != REG_R0); // for special. reg Must not be R0.
    id->idReg1(destReg);
    id->idInsOpt(INS_OPTS_RC);
    id->idCodeSize(2 * sizeof(code_t)); // auipc + load/addi

    // TODO-RISCV64: this maybe deleted.
    id->idSetIsBound(); // We won't patch address since we will know the exact distance
                        // once JIT code and data are allocated together.

    assert(addrReg == REG_NA); // NOTE: for RISV64, not support addrReg != REG_NA.

    id->idAddr()->iiaFieldHnd = fldHnd;

    appendToCurIG(id);
}

void emitter::emitIns_R_AR(instruction ins, emitAttr attr, regNumber ireg, regNumber reg, int offs)
{
    NYI_RISCV64("emitIns_R_AR-----unimplemented/unused on RISCV64 yet----");
}

// This computes address from the immediate which is relocatable.
void emitter::emitIns_R_AI(instruction  ins,
                           emitAttr     attr,
                           regNumber    dataReg,
                           regNumber    addrReg,
                           ssize_t addr DEBUGARG(size_t targetHandle) DEBUGARG(GenTreeFlags gtFlags))
{
    assert(EA_IS_RELOC(attr));
    assert(emitComp->opts.compReloc || (CorInfoReloc::RELATIVE32 == emitComp->eeGetRelocTypeHint((void*)addr)));
    assert(ins == INS_addi || emitInsIsLoadOrStore(ins));
    assert(emitInsIsStore(ins) || isFloatReg(dataReg) || (dataReg == REG_ZERO) || (dataReg == addrReg));
    assert(isGeneralRegister(addrReg));
    // 2-ins:
    //   auipc  addrReg, off-hi-20bits
    //   ins    dataReg, addrReg, off-lo-12bits

    instrDesc* id = emitNewInstr(attr);

    id->idIns(ins);
    id->idReg1(dataReg);
    id->idReg2(addrReg);

    id->idInsOpt(INS_OPTS_RELOC);
    id->idAddr()->iiaAddr = (BYTE*)addr;
    id->idCodeSize(8);

    appendToCurIG(id);
}

/*****************************************************************************
 *
 *  Record that a jump instruction uses the short encoding
 *
 */
void emitter::emitSetShortJump(instrDescJmp* id)
{
    if (id->idjKeepLong)
        return;

    assert(emitIsCmpJump(id) || emitIsUncondJump(id));
    id->idCodeSize(sizeof(code_t)); // single 32-bit instruction
    id->idjShort = true;

#if DEBUG_EMIT
    if (id->idDebugOnlyInfo()->idNum == (unsigned)INTERESTING_JUMP_NUM || INTERESTING_JUMP_NUM == 0)
    {
        printf("[8] Converting jump %u to short\n", id->idDebugOnlyInfo()->idNum);
    }
#endif // DEBUG_EMIT
}

/*****************************************************************************
 *
 *  Record that a jump instruction uses the medium encoding
 *
 */
void emitter::emitSetMediumJump(instrDescJmp* id)
{
    if (id->idjKeepLong)
        return;

#if DEBUG_EMIT
    if (id->idDebugOnlyInfo()->idNum == (unsigned)INTERESTING_JUMP_NUM || INTERESTING_JUMP_NUM == 0)
    {
        printf("[9] Converting jump %u to medium\n", id->idDebugOnlyInfo()->idNum);
    }
#endif // DEBUG_EMIT

    assert(emitIsCmpJump(id));
    id->idCodeSize(2 * sizeof(code_t)); // two 32-bit instructions
    id->idjShort = false;
}

/*****************************************************************************
 *
 *  Add a label instruction.
 */

void emitter::emitIns_R_L(instruction ins, emitAttr attr, BasicBlock* dst, regNumber reg)
{
    assert(dst->HasFlag(BBF_HAS_LABEL));

    // 2-ins:
    //   auipc reg, offset-hi20
    //   addi  reg, reg, offset-lo12

    instrDesc* id = emitNewInstr(attr);

    id->idIns(ins);
    id->idInsOpt(INS_OPTS_RL);
    id->idAddr()->iiaBBlabel = dst;

    if (emitComp->opts.compReloc)
        id->idSetIsDspReloc();

    id->idCodeSize(2 * sizeof(code_t));
    id->idReg1(reg);

#ifdef DEBUG
    // Mark the catch return
    if (emitComp->compCurBB->KindIs(BBJ_EHCATCHRET))
    {
        id->idDebugOnlyInfo()->idCatchRet = true;
    }
#endif // DEBUG

    appendToCurIG(id);
}

//------------------------------------------------------------------------
// emitIns_R_R_Addr: emit instruction sequence for a long (address pointer) immediate
//
// If the address is approximately in range, emits a PC-relative combo with a relocation:
//     auipc regAddr, offset_hi20
//     ins   regData, regAddr, offset_lo12
// Otherwise, synthesizes an absolute address combo:
//     li regAddr, (addr - lo12)
//     ins regData, regAddr, lo12
//
// Arguments:
//    ins     - an instruction that is a 64-bit adder with 12-bit immediate (addi/load/store)
//    attr    - attribute
//    regData - destination register for addi/load, source for stores
//    regAddr - temporary register to synthesize the address into (pass same as regData if possible)
//    addr    - the address
//
void emitter::emitIns_R_R_Addr(instruction ins, emitAttr attr, regNumber regData, regNumber regAddr, void* addr)
{
    assert(ins == INS_addi || emitInsIsLoadOrStore(ins));
    assert(!EA_IS_RELOC(attr) && EA_SIZE(attr) == EA_PTRSIZE);

    if (IsAddressInRange(addr))
    {
        attr = EA_SET_FLG(attr, EA_PTR_DSP_RELOC);
        emitIns_R_AI(ins, attr, regData, regAddr, (ssize_t)addr);
    }
    else
    {
        ssize_t imm  = (ssize_t)addr;
        ssize_t lo12 = (imm << (64 - 12)) >> (64 - 12);
        imm -= lo12;
        emitLoadImmediate(attr, regAddr, imm);
        emitIns_R_R_I(ins, attr, regData, regAddr, lo12);
    }
}

void emitter::emitIns_J(instruction ins, BasicBlock* dst)
{
    assert(emitIsUncondJump(ins));
    regNumber linkReg = (ins == INS_jal) ? REG_RA : REG_ZERO;
    emitIns_Jump(ins, dst, linkReg, REG_ZERO);
}

void emitter::emitIns_J_cond_la(instruction ins, BasicBlock* dst, regNumber reg1, regNumber reg2)
{
    assert(emitIsCmpJump(ins));
    assert((ins != INS_bnez && ins != INS_beqz) || (reg2 == REG_ZERO));
    emitIns_Jump(ins, dst, reg1, reg2);
}

void emitter::emitIns_Jump(instruction ins, BasicBlock* dst, regNumber reg1, regNumber reg2)
{
    assert(dst != nullptr);
    assert(dst->HasFlag(BBF_HAS_LABEL));

    instrDescJmp* id = emitNewInstrJmp();
    id->idIns(ins);
    id->idReg1(reg1);
    id->idReg2(reg2);
    // Start from the worst case: "[branch (reversed);] auipc; jalr"
    id->idjShort = false;
    id->idCodeSize((emitIsCmpJump(id) ? 3 : 2) * sizeof(code_t));
    id->idInsOpt(INS_OPTS_JUMP);
    id->idAddr()->iiaBBlabel = dst;

    id->idjKeepLong = emitComp->fgInDifferentRegions(emitComp->compCurBB, dst);
#ifdef DEBUG
    if (emitComp->opts.compLongAddress) // Force long branches
        id->idjKeepLong = 1;
#endif // DEBUG

    /* Record the jump's IG and offset within it */
    id->idjIG   = emitCurIG;
    id->idjOffs = emitCurIGsize;

    /* Append this jump to this IG's jump list */
    id->idjNext      = emitCurIGjmpList;
    emitCurIGjmpList = id;

#if EMITTER_STATS
    emitTotalIGjmps++;
#endif

    /* Figure out the max. size of the jump/call instruction */
    insGroup* tgt = (insGroup*)emitCodeGetCookie(dst);
    if (!id->idjKeepLong && (tgt != nullptr))
    {
        /* This is a backward jump - figure out the distance */
        UNATIVE_OFFSET srcOffs = emitCurCodeOffset + emitCurIGsize;

        /* Compute the distance estimate */
        int jmpDist = srcOffs - tgt->igOffs;
        assert(jmpDist >= 0);

        if (emitIsCmpJump(id))
        {
            if (B_DIST_SMALL_MAX_NEG <= -jmpDist)
            {
                emitSetShortJump(id);
            }
            else if (J_DIST_SMALL_MAX_NEG <= -jmpDist - sizeof(code_t)) // the PC will be taken after the reversed
                                                                        // branch
            {
                emitSetMediumJump(id);
            }
        }
        else
        {
            if (J_DIST_SMALL_MAX_NEG <= -jmpDist)
            {
                emitSetShortJump(id);
            }
        }
    }

    appendToCurIG(id);
}

static inline constexpr unsigned WordMask(uint8_t bits);

/*****************************************************************************
 *
 *  Emits load of 64-bit constant to register.
 *
 */
void emitter::emitLoadImmediate(emitAttr size, regNumber reg, ssize_t imm)
{
    assert(!EA_IS_RELOC(size));
    assert(isGeneralRegister(reg));

    if (isValidSimm12(imm))
    {
        emitIns_R_R_I(INS_addi, size, reg, REG_R0, imm & 0xFFF);
        return;
    }

    /* The following algorithm works based on the following equation:
     * `imm = high32 + offset1` OR `imm = high32 - offset2`
     *
     * high32 will be loaded with `lui + addiw`, while offset
     * will be loaded with `slli + addi` in 11-bits chunks
     *
     * First, determine at which position to partition imm into high32 and offset,
     * so that it yields the least instruction.
     * Where high32 = imm[y:x] and imm[63:y] are all zeroes or all ones.
     *
     * From the above equation, the value of offset1 & offset2 are:
     * -> offset1 = imm[x-1:0]
     * -> offset2 = ~(imm[x-1:0] - 1)
     * The smaller offset should yield the least instruction. (is this correct?) */

    // STEP 1: Determine x & y

    int x;
    int y;
    if (((uint64_t)imm >> 63) & 0b1)
    {
        // last one position from MSB
        y = 63 - BitOperations::LeadingZeroCount((uint64_t)~imm) + 1;
    }
    else
    {
        // last zero position from MSB
        y = 63 - BitOperations::LeadingZeroCount((uint64_t)imm) + 1;
    }
    if (imm & 0b1)
    {
        // first zero position from LSB
        x = BitOperations::TrailingZeroCount((uint64_t)~imm);
    }
    else
    {
        // first one position from LSB
        x = BitOperations::TrailingZeroCount((uint64_t)imm);
    }

    // STEP 2: Determine whether to utilize SRLI or not.

    /* SRLI can be utilized when the input has the following pattern:
     *
     * 0...01...10...x
     * <-n-><-m->
     *
     * It will emit instructions to load the left shifted immidiate then
     * followed by a single SRLI instruction.
     *
     * Since it adds 1 instruction, loading the new form should at least remove
     * two instruction. Two instructions can be removed IF:
     *  1. y - x > 31, AND
     *  2. (b - a) < 32, OR
     *  3. (b - a) - (y - x) >= 11
     *
     * Visualization aid:
     * - Original immidiate
     *   0...01...10...x
     *       y       <-x
     * - Left shifted immidiate
     *   1...10...x0...0
     *       b  <-a
     * */

    constexpr int absMaxInsCount  = instrDescLoadImm::absMaxInsCount;
    constexpr int prefMaxInsCount = 5;
    assert(prefMaxInsCount <= absMaxInsCount);

    // If we generate more instructions than the prefered maximum instruction count, we'll instead use emitDataConst +
    // emitIns_R_C combination.
    int insCountLimit = prefMaxInsCount;
    // If we are currently generating prolog / epilog, we are currently not inside a method block, therefore, we should
    // not use the emitDataConst + emitIns_R_C combination.
    if (emitComp->compGeneratingProlog || emitComp->compGeneratingEpilog)
    {
        insCountLimit = absMaxInsCount;
    }

    bool     utilizeSRLI     = false;
    int      srliShiftAmount = 0;
    uint64_t originalImm     = imm;
    bool     cond1           = (y - x) > 31;
    if ((((uint64_t)imm >> 63) & 0b1) == 0 && cond1)
    {
        srliShiftAmount  = BitOperations::LeadingZeroCount((uint64_t)imm);
        uint64_t tempImm = (uint64_t)imm << srliShiftAmount;
        int      m       = BitOperations::LeadingZeroCount(~tempImm);
        int      b       = 64 - m;
        int      a       = BitOperations::TrailingZeroCount(tempImm);
        bool     cond2   = (b - a) < 32;
        bool     cond3   = ((y - x) - (b - a)) >= 11;
        if (cond2 || cond3)
        {
            imm         = tempImm;
            y           = b;
            x           = a;
            utilizeSRLI = true;
            insCountLimit -= 1;
        }
    }

    assert(y >= x);
    assert((1 <= y) && (y <= 63));
    assert((1 <= x) && (x <= 63));

    if (y < 32)
    {
        y = 31;
        x = 0;
    }
    else if ((y - x) < 31)
    {
        y = x + 31;
    }
    else
    {
        x = y - 31;
    }

    uint32_t high32 = ((int64_t)imm >> x) & WordMask(32);

    // STEP 3: Determine whether to use high32 + offset1 or high32 - offset2

    /* TODO: Instead of using subtract / add mode, assume that we're always adding
     * 12-bit chunks. However, if we encounter such 12-bit chunk with MSB == 1,
     * add 1 to the previous chunk, and add the 12-bit chunk as is, which
     * essentially does a subtraction. It will generate the least instruction to
     * load offset.
     * See the following discussion:
     * https://github.com/dotnet/runtime/pull/113250#discussion_r1987576070 */

    uint32_t offset1        = imm & WordMask((uint8_t)x);
    uint32_t offset2        = (~(offset1 - 1)) & WordMask((uint8_t)x);
    uint32_t offset         = offset1;
    bool     isSubtractMode = false;

    if ((high32 == 0x7FFFFFFF) && (y != 63))
    {
        /* Handle corner case: we cannot do subtract mode if high32 == 0x7FFFFFFF
         * Since adding 1 to it will change the sign bit. Instead, shift x and y
         * to the left by one. */
        int      newX       = x + 1;
        uint32_t newOffset1 = imm & WordMask((uint8_t)newX);
        uint32_t newOffset2 = (~(newOffset1 - 1)) & WordMask((uint8_t)newX);
        if (newOffset2 < offset1)
        {
            x              = newX;
            high32         = ((int64_t)imm >> x) & WordMask(32);
            offset2        = newOffset2;
            isSubtractMode = true;
        }
    }
    else if (offset2 < offset1)
    {
        isSubtractMode = true;
    }

    if (isSubtractMode)
    {
        offset = offset2;
        high32 = (high32 + 1) & WordMask(32);
    }

    assert(absMaxInsCount >= 2);
    int         numberOfInstructions = 0;
    instruction ins[absMaxInsCount];
    int32_t     values[absMaxInsCount];

    // STEP 4: Generate instructions to load high32

    uint32_t upper    = (high32 >> 12) & WordMask(20);
    uint32_t lower    = high32 & WordMask(12);
    int      lowerMsb = (lower >> 11) & 0b1;
    if (lowerMsb == 1)
    {
        upper += 1;
        upper &= WordMask(20);
    }
    if (upper != 0)
    {
        ins[numberOfInstructions]    = INS_lui;
        values[numberOfInstructions] = ((upper >> 19) & 0b1) ? (upper + 0xFFF00000) : upper;
        numberOfInstructions += 1;
    }
    if (lower != 0)
    {
        ins[numberOfInstructions]    = INS_addiw;
        values[numberOfInstructions] = lower;
        numberOfInstructions += 1;
    }

    // STEP 5: Generate instructions to load offset in 11-bits chunks

    int chunkLsbPos = (x < 11) ? 0 : (x - 11);
    int shift       = (x < 11) ? x : 11;
    int chunkMask   = (x < 11) ? WordMask((uint8_t)x) : WordMask(11);
    while (true)
    {
        uint32_t chunk = (offset >> chunkLsbPos) & chunkMask;

        if (chunk != 0)
        {
            /* We could move our 11 bit chunk window to the right for as many as the
             * leading zeros.*/
            int leadingZerosOn11BitsChunk = 11 - (32 - BitOperations::LeadingZeroCount(chunk));
            if (leadingZerosOn11BitsChunk > 0)
            {
                int maxAdditionalShift =
                    (chunkLsbPos < leadingZerosOn11BitsChunk) ? chunkLsbPos : leadingZerosOn11BitsChunk;
                chunkLsbPos -= maxAdditionalShift;
                shift += maxAdditionalShift;
                chunk = (offset >> chunkLsbPos) & chunkMask;
            }

            numberOfInstructions += 2;
            if (numberOfInstructions > insCountLimit)
            {
                break;
            }
            ins[numberOfInstructions - 2]    = INS_slli;
            values[numberOfInstructions - 2] = shift;
            if (isSubtractMode)
            {
                ins[numberOfInstructions - 1]    = INS_addi;
                values[numberOfInstructions - 1] = -(int32_t)chunk;
            }
            else
            {
                ins[numberOfInstructions - 1]    = INS_addi;
                values[numberOfInstructions - 1] = chunk;
            }
            shift = 0;
        }
        if (chunkLsbPos == 0)
        {
            break;
        }
        shift += (chunkLsbPos < 11) ? chunkLsbPos : 11;
        chunkMask = (chunkLsbPos < 11) ? (chunkMask >> (11 - chunkLsbPos)) : WordMask(11);
        chunkLsbPos -= (chunkLsbPos < 11) ? chunkLsbPos : 11;
    }
    if (shift > 0)
    {
        numberOfInstructions += 1;
        if (numberOfInstructions <= insCountLimit)
        {
            ins[numberOfInstructions - 1]    = INS_slli;
            values[numberOfInstructions - 1] = shift;
        }
    }

    // STEP 6: Determine whether to use emitDataConst or emit generated instructions

    if (numberOfInstructions <= insCountLimit)
    {
        instrDescLoadImm* id = static_cast<instrDescLoadImm*>(emitNewInstrLoadImm(size, originalImm));
        id->idReg1(reg);
        memcpy(id->ins, ins, sizeof(instruction) * numberOfInstructions);
        memcpy(id->values, values, sizeof(int32_t) * numberOfInstructions);
        if (utilizeSRLI)
        {
            numberOfInstructions += 1;
            assert(numberOfInstructions < absMaxInsCount);
            id->ins[numberOfInstructions - 1]    = INS_srli;
            id->values[numberOfInstructions - 1] = srliShiftAmount;
        }
        id->idCodeSize(numberOfInstructions * 4);
        id->idIns(id->ins[numberOfInstructions - 1]);

        appendToCurIG(id);
    }
    else if (EA_SIZE(size) == EA_PTRSIZE)
    {
        assert(!emitComp->compGeneratingProlog && !emitComp->compGeneratingEpilog);
        auto constAddr = emitDataConst(&originalImm, sizeof(long), sizeof(long), TYP_LONG);
        emitIns_R_C(INS_ld, EA_PTRSIZE, reg, REG_NA, emitComp->eeFindJitDataOffs(constAddr));
    }
    else
    {
        assert(false && "If number of instruction exceeds MAX_NUM_OF_LOAD_IMM_INS, imm must be 8 bytes");
    }
}

/*****************************************************************************
 *
 *  Add a call instruction (direct or indirect).
 *      argSize<0 means that the caller will pop the arguments
 *
 * The other arguments are interpreted depending on callType as shown:
 * Unless otherwise specified, ireg,xreg,xmul,disp should have default values.
 *
 * EC_FUNC_TOKEN       : addr is the method address
 *
 * If callType is one of these emitCallTypes, addr has to be NULL.
 * EC_INDIR_R          : "call ireg".
 *
 * noSafePoint - force not making this call a safe point in partially interruptible code
 *
 */

void emitter::emitIns_Call(const EmitCallParams& params)
{
    /* Sanity check the arguments depending on callType */

    assert(params.callType < EC_COUNT);
    assert(isGeneralRegister(params.ireg));
    assert(params.callType < EC_INDIR_R || params.addr == nullptr);

    // RISCV64 never uses these
    assert(params.xreg == REG_NA && params.xmul == 0 && params.disp == 0);

    // Our stack level should be always greater than the bytes of arguments we push. Just
    // a sanity test.
    assert((unsigned)std::abs(params.argSize) <= codeGen->genStackLevel);

    // Trim out any callee-trashed registers from the live set.
    regMaskTP savedSet  = emitGetGCRegsSavedOrModified(params.methHnd);
    regMaskTP gcrefRegs = params.gcrefRegs & savedSet;
    regMaskTP byrefRegs = params.byrefRegs & savedSet;

#ifdef DEBUG
    if (EMIT_GC_VERBOSE)
    {
        printf("Call: GCvars=%s ", VarSetOps::ToString(emitComp, params.ptrVars));
        dumpConvertedVarSet(emitComp, params.ptrVars);
        printf(", gcrefRegs=");
        printRegMaskInt(gcrefRegs);
        emitDispRegSet(gcrefRegs);
        printf(", byrefRegs=");
        printRegMaskInt(byrefRegs);
        emitDispRegSet(byrefRegs);
        printf("\n");
    }
#endif

    ssize_t jalrOffset = 0;
    if (params.callType == EC_FUNC_TOKEN && !IsAddressInRange(params.addr))
    {
        // Load upper bits of the absolute call address into a register:
        // li   ireg, addr_upper
        // jalr zero/ra, ireg, addr_lo12 (emitted below)
        assert(params.addr != nullptr);
        ssize_t imm = (ssize_t)params.addr;
        jalrOffset  = (imm << (64 - 12)) >> (64 - 12); // low 12-bits, sign-extended
        imm -= jalrOffset;
        emitLoadImmediate(EA_PTRSIZE, params.ireg, imm); // upper bits
    }

    /* Managed RetVal: emit sequence point for the call */
    if (emitComp->opts.compDbgInfo && params.debugInfo.GetLocation().IsValid())
    {
        codeGen->genIPmappingAdd(IPmappingDscKind::Normal, params.debugInfo, false);
    }

    /*
        We need to allocate the appropriate instruction descriptor based
        on whether this is a direct/indirect call, and whether we need to
        record an updated set of live GC variables.
     */
    instrDesc* id;

    assert(params.argSize % REGSIZE_BYTES == 0);
    int argCnt = (int)(params.argSize / (int)REGSIZE_BYTES);

    if (params.callType >= EC_INDIR_R)
    {
        /* Indirect call, virtual calls */

        assert(params.callType == EC_INDIR_R);

        id = emitNewInstrCallInd(argCnt, params.disp, params.ptrVars, gcrefRegs, byrefRegs, params.retSize,
                                 params.secondRetSize, params.hasAsyncRet);
    }
    else
    {
        /* Helper/static/nonvirtual/function calls (direct or through handle),
           and calls to an absolute addr. */

        assert(params.callType == EC_FUNC_TOKEN);

        id = emitNewInstrCallDir(argCnt, params.ptrVars, gcrefRegs, byrefRegs, params.retSize, params.secondRetSize,
                                 params.hasAsyncRet);
    }

    /* Update the emitter's live GC ref sets */

    // If the method returns a GC ref, mark RBM_INTRET appropriately
    if (params.retSize == EA_GCREF)
    {
        gcrefRegs |= RBM_INTRET;
    }
    else if (params.retSize == EA_BYREF)
    {
        byrefRegs |= RBM_INTRET;
    }

    // If is a multi-register return method is called, mark RBM_INTRET_1 appropriately
    if (params.secondRetSize == EA_GCREF)
    {
        gcrefRegs |= RBM_INTRET_1;
    }
    else if (params.secondRetSize == EA_BYREF)
    {
        byrefRegs |= RBM_INTRET_1;
    }

    VarSetOps::Assign(emitComp, emitThisGCrefVars, params.ptrVars);
    emitThisGCrefRegs = gcrefRegs;
    emitThisByrefRegs = byrefRegs;

    // for the purpose of GC safepointing tail-calls are not real calls
    id->idSetIsNoGC(params.isJump || params.noSafePoint || emitNoGChelper(params.methHnd));

    /* Set the instruction - special case jumping a function */
    id->idIns(INS_jalr);

    id->idInsOpt(INS_OPTS_C);

    /* Record the address: method, indirection, or funcptr */
    if ((params.callType == EC_INDIR_R) || (params.callType == EC_FUNC_TOKEN && !IsAddressInRange(params.addr)))
    {
        /* This is an indirect call (either a virtual call or func ptr call) */

        //      jalr zero/ra, ireg, offset

        id->idSetIsCallRegPtr();

        regNumber reg_jalr = params.isJump ? REG_R0 : REG_RA;
        id->idReg4(reg_jalr);
        id->idReg3(params.ireg); // NOTE: for EC_INDIR_R, using idReg3.
        id->idSmallCns(jalrOffset);
        id->idCodeSize(4);
    }
    else
    {
        /* This is a simple direct call: "call helper/method/addr" */

        //      auipc t2/ra, offset-hi20
        //      jalr zero/ra, t2/ra, offset-lo12

        assert(params.callType == EC_FUNC_TOKEN);
        assert(params.addr != nullptr);
        assert(IsAddressInRange(params.addr));

        void* addr =
            (void*)(((size_t)params.addr) + (params.isJump ? 0 : 1)); // NOTE: low-bit0 is used for jalr ra/r0,rd,0
        id->idAddr()->iiaAddr = (BYTE*)addr;
        id->idCodeSize(2 * sizeof(code_t));
        id->idSetIsDspReloc();
    }

#ifdef DEBUG
    if (EMIT_GC_VERBOSE)
    {
        if (id->idIsLargeCall())
        {
            printf("[%02u] Rec call GC vars = %s\n", id->idDebugOnlyInfo()->idNum,
                   VarSetOps::ToString(emitComp, ((instrDescCGCA*)id)->idcGCvars));
        }
    }
#endif // DEBUG

    if (m_debugInfoSize > 0)
    {
        INDEBUG(id->idDebugOnlyInfo()->idCallSig = params.sigInfo);
        id->idDebugOnlyInfo()->idMemCookie = reinterpret_cast<size_t>(params.methHnd); // method token
    }

#ifdef LATE_DISASM
    if (params.addr != nullptr)
    {
        codeGen->getDisAssembler().disSetMethod((size_t)params.addr, params.methHnd);
    }
#endif // LATE_DISASM

    appendToCurIG(id);
}

/*****************************************************************************
 *
 *  Output a call instruction.
 */

unsigned emitter::emitOutputCall(const insGroup* ig, BYTE* dst, instrDesc* id)
{
    regMaskTP gcrefRegs;
    regMaskTP byrefRegs;

    VARSET_TP GCvars(VarSetOps::UninitVal());

    // Is this a "fat" call descriptor?
    if (id->idIsLargeCall())
    {
        instrDescCGCA* idCall = (instrDescCGCA*)id;
        gcrefRegs             = idCall->idcGcrefRegs;
        byrefRegs             = idCall->idcByrefRegs;
        VarSetOps::Assign(emitComp, GCvars, idCall->idcGCvars);
    }
    else
    {
        assert(!id->idIsLargeDsp());
        assert(!id->idIsLargeCns());

        gcrefRegs = emitDecodeCallGCregs(id);
        byrefRegs = 0;
        VarSetOps::AssignNoCopy(emitComp, GCvars, VarSetOps::MakeEmpty(emitComp));
    }

    /* We update the GC info before the call as the variables cannot be
        used by the call. Killing variables before the call helps with
        boundary conditions if the call is CORINFO_HELP_THROW - see bug 50029.
        If we ever track aliased variables (which could be used by the
        call), we would have to keep them alive past the call. */

    emitUpdateLiveGCvars(GCvars, dst);
#ifdef DEBUG
    // NOTEADD:
    // Output any delta in GC variable info, corresponding to the before-call GC var updates done above.
    if (EMIT_GC_VERBOSE || emitComp->opts.disasmWithGC)
    {
        emitDispGCVarDelta(); // define in emit.cpp
    }
#endif // DEBUG

    assert(id->idIns() == INS_jalr);
    BYTE* origDst = dst;
    if (id->idIsCallRegPtr())
    { // EC_INDIR_R
        ssize_t offset = id->idSmallCns();
        dst += emitOutput_ITypeInstr(dst, INS_jalr, id->idReg4(), id->idReg3(), TrimSignedToImm12(offset));
    }
    else
    {
        size_t addr = (size_t)(id->idAddr()->iiaAddr); // get addr.

        regNumber linkReg = (regNumber)(addr & 1);
        assert(linkReg == REG_ZERO || linkReg == REG_RA);
        addr -= linkReg;
        assert((addr & 1) == 0);
        regNumber tempReg = (linkReg == REG_ZERO) ? REG_DEFAULT_HELPER_CALL_TARGET : REG_RA;

        dst += emitOutput_UTypeInstr(dst, INS_auipc, tempReg, 0);
        emitGCregDeadUpd(tempReg, dst);
        dst += emitOutput_ITypeInstr(dst, INS_jalr, linkReg, tempReg, 0);

        assert(id->idIsDspReloc());
        emitRecordRelocation(origDst, (BYTE*)addr, CorInfoReloc::RISCV64_CALL_PLT);
    }

    // If the method returns a GC ref, mark INTRET (A0) appropriately.
    if (id->idGCref() == GCT_GCREF)
    {
        gcrefRegs |= RBM_INTRET;
    }
    else if (id->idGCref() == GCT_BYREF)
    {
        byrefRegs |= RBM_INTRET;
    }

    // If is a multi-register return method is called, mark INTRET_1 (A1) appropriately
    if (id->idIsLargeCall())
    {
        instrDescCGCA* idCall = (instrDescCGCA*)id;
        if (idCall->idSecondGCref() == GCT_GCREF)
        {
            gcrefRegs |= RBM_INTRET_1;
        }
        else if (idCall->idSecondGCref() == GCT_BYREF)
        {
            byrefRegs |= RBM_INTRET_1;
        }
        if (idCall->hasAsyncContinuationRet())
        {
            gcrefRegs |= RBM_ASYNC_CONTINUATION_RET;
        }
    }

    // If the GC register set has changed, report the new set.
    if (gcrefRegs != emitThisGCrefRegs)
    {
        emitUpdateLiveGCregs(GCT_GCREF, gcrefRegs, dst);
    }
    // If the Byref register set has changed, report the new set.
    if (byrefRegs != emitThisByrefRegs)
    {
        emitUpdateLiveGCregs(GCT_BYREF, byrefRegs, dst);
    }

    // Some helper calls may be marked as not requiring GC info to be recorded.
    if (!id->idIsNoGC())
    {
        // On RISCV64, as on AMD64 and LOONGARCH64, we don't change the stack pointer to push/pop args.
        // So we're not really doing a "stack pop" here (note that "args" is 0), but we use this mechanism
        // to record the call for GC info purposes.  (It might be best to use an alternate call,
        // and protect "emitStackPop" under the EMIT_TRACK_STACK_DEPTH preprocessor variable.)
        emitStackPop(dst, /*isCall*/ true, sizeof(code_t), /*args*/ 0);

        // Do we need to record a call location for GC purposes?
        //
        if (!emitFullGCinfo)
        {
            emitRecordGCcall(dst, sizeof(code_t));
        }
    }

    assert(dst > origDst);
    assert((dst - origDst) <= UINT_MAX);
    return (unsigned)(dst - origDst);
}

/*****************************************************************************
 *
 *  Emit a 16/32-bit RISCV64 instruction
 */

unsigned emitter::emitOutput_Instr(BYTE* dst, code_t code) const
{
    assert(dst != nullptr);
    static_assert(sizeof(code_t) == 4, "code_t must be 4 bytes");
    unsigned codeSize = Is32BitInstruction((WORD)code) ? 4 : 2;
    assert((codeSize == 4) || ((code >> 16) == 0));
    memcpy(dst + writeableOffset, &code, codeSize);
    return codeSize;
}

static inline void assertCodeLength(size_t code, uint8_t size)
{
    assert((code >> size) == 0);
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 R-Type instruction
 *
 *  Note: Instruction types as per RISC-V Spec, Chapter "RV32/64G Instruction Set Listings"
 *  R-Type layout:
 *  31-------25-24---20-19--15-14------12-11-----------7-6------------0
 *  | funct7   |  rs2  | rs1  |  funct3  |      rd      |   opcode    |
 *  -------------------------------------------------------------------
 */

/*static*/ emitter::code_t emitter::insEncodeRTypeInstr(
    unsigned opcode, unsigned rd, unsigned funct3, unsigned rs1, unsigned rs2, unsigned funct7)
{
    assertCodeLength(opcode, 7);
    assertCodeLength(rd, 5);
    assertCodeLength(funct3, 3);
    assertCodeLength(rs1, 5);
    assertCodeLength(rs2, 5);
    assertCodeLength(funct7, 7);

    return opcode | (rd << 7) | (funct3 << 12) | (rs1 << 15) | (rs2 << 20) | (funct7 << 25);
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 I-Type instruction
 *
 *  Note: Instruction types as per RISC-V Spec, Chapter "RV32/64G Instruction Set Listings"
 *  I-Type layout:
 *  31------------20-19-----15-14------12-11-----------7-6------------0
 *  |   imm[11:0]   |   rs1   |  funct3  |      rd      |   opcode    |
 *  -------------------------------------------------------------------
 */

/*static*/ emitter::code_t emitter::insEncodeITypeInstr(
    unsigned opcode, unsigned rd, unsigned funct3, unsigned rs1, unsigned imm12)
{
    assertCodeLength(opcode, 7);
    assertCodeLength(rd, 5);
    assertCodeLength(funct3, 3);
    assertCodeLength(rs1, 5);
    // This assert may be triggered by the untrimmed signed integers. Please refer to the TrimSigned helpers
    assertCodeLength(imm12, 12);

    return opcode | (rd << 7) | (funct3 << 12) | (rs1 << 15) | (imm12 << 20);
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 S-Type instruction
 *
 *  Note: Instruction types as per RISC-V Spec, Chapter "RV32/64G Instruction Set Listings"
 *  S-Type layout:
 *  31-------25-24---20-19--15-14------12-11-----------7-6------------0
 *  |imm[11:5] |  rs2  | rs1  |  funct3  |   imm[4:0]   |   opcode    |
 *  -------------------------------------------------------------------
 */

/*static*/ emitter::code_t emitter::insEncodeSTypeInstr(
    unsigned opcode, unsigned funct3, unsigned rs1, unsigned rs2, unsigned imm12)
{
    static constexpr unsigned kLoMask = 0x1f; // 0b00011111
    static constexpr unsigned kHiMask = 0x7f; // 0b01111111

    assertCodeLength(opcode, 7);
    assertCodeLength(funct3, 3);
    assertCodeLength(rs1, 5);
    assertCodeLength(rs2, 5);
    // This assert may be triggered by the untrimmed signed integers. Please refer to the TrimSigned helpers
    assertCodeLength(imm12, 12);

    unsigned imm12Lo = imm12 & kLoMask;
    unsigned imm12Hi = (imm12 >> 5) & kHiMask;

    return opcode | (imm12Lo << 7) | (funct3 << 12) | (rs1 << 15) | (rs2 << 20) | (imm12Hi << 25);
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 U-Type instruction
 *
 *  Note: Instruction types as per RISC-V Spec, Chapter "RV32/64G Instruction Set Listings"
 *  U-Type layout:
 *  31---------------------------------12-11-----------7-6------------0
 *  |             imm[31:12]             |      rd      |   opcode    |
 *  -------------------------------------------------------------------
 */

/*static*/ emitter::code_t emitter::insEncodeUTypeInstr(unsigned opcode, unsigned rd, unsigned imm20)
{
    assertCodeLength(opcode, 7);
    assertCodeLength(rd, 5);
    // This assert may be triggered by the untrimmed signed integers. Please refer to the TrimSigned helpers
    assertCodeLength(imm20, 20);

    return opcode | (rd << 7) | (imm20 << 12);
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 B-Type instruction
 *
 *  Note: Instruction types as per RISC-V Spec, Chapter "RV32/64G Instruction Set Listings"
 *  B-Type layout:
 *  31-------30-----25-24-20-19-15-14--12-11-------8----7----6--------0
 *  |imm[12]|imm[10:5]| rs2 | rs1 |funct3|  imm[4:1]|imm[11]| opcode  |
 *  -------------------------------------------------------------------
 */

/*static*/ emitter::code_t emitter::insEncodeBTypeInstr(
    unsigned opcode, unsigned funct3, unsigned rs1, unsigned rs2, unsigned imm13)
{
    static constexpr unsigned kLoSectionMask = 0x0f; // 0b00001111
    static constexpr unsigned kHiSectionMask = 0x3f; // 0b00111111
    static constexpr unsigned kBitMask       = 0x01;

    assertCodeLength(opcode, 7);
    assertCodeLength(funct3, 3);
    assertCodeLength(rs1, 5);
    assertCodeLength(rs2, 5);
    // This assert may be triggered by the untrimmed signed integers. Please refer to the TrimSigned helpers
    assertCodeLength(imm13, 13);
    assert((imm13 & 0x01) == 0);

    unsigned imm12          = imm13 >> 1;
    unsigned imm12LoSection = imm12 & kLoSectionMask;
    unsigned imm12LoBit     = (imm12 >> 10) & kBitMask;
    unsigned imm12HiSection = (imm12 >> 4) & kHiSectionMask;
    unsigned imm12HiBit     = (imm12 >> 11) & kBitMask;

    return opcode | (imm12LoBit << 7) | (imm12LoSection << 8) | (funct3 << 12) | (rs1 << 15) | (rs2 << 20) |
           (imm12HiSection << 25) | (imm12HiBit << 31);
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 J-Type instruction
 *
 *  Note: Instruction types as per RISC-V Spec, Chapter "RV32/64G Instruction Set Listings"
 *  J-Type layout:
 *  31-------30--------21----20---19----------12-11----7-6------------0
 *  |imm[20]| imm[10:1]  |imm[11]|  imm[19:12]  |  rd   |   opcode    |
 *  -------------------------------------------------------------------
 */

/*static*/ emitter::code_t emitter::insEncodeJTypeInstr(unsigned opcode, unsigned rd, unsigned imm21)
{
    static constexpr unsigned kHiSectionMask = 0x3ff; // 0b1111111111
    static constexpr unsigned kLoSectionMask = 0xff;  // 0b11111111
    static constexpr unsigned kBitMask       = 0x01;

    assertCodeLength(opcode, 7);
    assertCodeLength(rd, 5);
    // This assert may be triggered by the untrimmed signed integers. Please refer to the TrimSigned helpers
    assertCodeLength(imm21, 21);
    assert((imm21 & 0x01) == 0);

    unsigned imm20          = imm21 >> 1;
    unsigned imm20HiSection = imm20 & kHiSectionMask;
    unsigned imm20HiBit     = (imm20 >> 19) & kBitMask;
    unsigned imm20LoSection = (imm20 >> 11) & kLoSectionMask;
    unsigned imm20LoBit     = (imm20 >> 10) & kBitMask;

    return opcode | (rd << 7) | (imm20LoSection << 12) | (imm20LoBit << 20) | (imm20HiSection << 21) |
           (imm20HiBit << 31);
}

/*****************************************************************************
 *
 *  Emit a 16-bit RISCV64C CR-Type instruction
 *
 *  Note: Instruction types as per RISC-V Spec, Chapter "Compressed Instruction Formats"
 *  CR Format:
 *  15-------------12-11-----------------7-6------------------2-1-----0
 *  |     funct4     |       rd/rs1       |        rs2         |  op  |
 *  -------------------------------------------------------------------
 */

/*static*/ emitter::code_t emitter::insEncodeCRTypeInstr(instruction ins, unsigned rdRs1, unsigned rs2)
{
    assert((INS_c_mv <= ins) && (ins <= INS_c_add));
    code_t insCode = emitInsCode(ins);

    assertCodeLength(insCode, 16);
    assertCodeLength(rdRs1, 5);
    assertCodeLength(rs2, 5);

    return insCode | (rs2 << 2) | (rdRs1 << 7);
}

/*****************************************************************************
 *
 *  Emit a 16-bit RISCV64C CA-Type instruction
 *
 *  Note: Instruction types as per RISC-V Spec, Chapter "Compressed Instruction Formats"
 *  CA Format:
 *  15-----------------------10-9----------7-6------5-4---------2-1---0
 *  |          funct6          |  rd'/rs1' | funct2 |    rs2'   | op |
 *  -------------------------------------------------------------------
 */

/*static*/ emitter::code_t emitter::insEncodeCATypeInstr(instruction ins, unsigned rdRs1Rvc, unsigned rs2Rvc)
{
    assert((INS_c_and <= ins) && (ins <= INS_c_subw));
    code_t insCode = emitInsCode(ins);

    assertCodeLength(insCode, 16);
    assertCodeLength(rdRs1Rvc, 3);
    assertCodeLength(rs2Rvc, 3);

    return insCode | (rs2Rvc << 2) | (rdRs1Rvc << 7);
}

static constexpr unsigned kInstructionOpcodeMask = 0x7f;
static constexpr unsigned kInstructionFunct3Mask = 0x7000;
static constexpr unsigned kInstructionFunct5Mask = 0xf8000000;
static constexpr unsigned kInstructionFunct7Mask = 0xfe000000;
static constexpr unsigned kInstructionFunct2Mask = 0x06000000;

#ifdef DEBUG

/*static*/ void emitter::emitOutput_RTypeInstr_SanityCheck(instruction ins, regNumber rd, regNumber rs1, regNumber rs2)
{
    switch (ins)
    {
        case INS_add:
        case INS_sub:
        case INS_sll:
        case INS_slt:
        case INS_sltu:
        case INS_xor:
        case INS_srl:
        case INS_sra:
        case INS_or:
        case INS_and:
        case INS_addw:
        case INS_subw:
        case INS_sllw:
        case INS_srlw:
        case INS_sraw:
        case INS_mul:
        case INS_mulh:
        case INS_mulhsu:
        case INS_mulhu:
        case INS_div:
        case INS_divu:
        case INS_rem:
        case INS_remu:
        case INS_mulw:
        case INS_divw:
        case INS_divuw:
        case INS_remw:
        case INS_remuw:
            assert(isGeneralRegisterOrR0(rd));
            assert(isGeneralRegisterOrR0(rs1));
            assert(isGeneralRegisterOrR0(rs2));
            break;
        case INS_fsgnj_s:
        case INS_fsgnjn_s:
        case INS_fsgnjx_s:
        case INS_fmin_s:
        case INS_fmax_s:
        case INS_fsgnj_d:
        case INS_fsgnjn_d:
        case INS_fsgnjx_d:
        case INS_fmin_d:
        case INS_fmax_d:
            assert(isFloatReg(rd));
            assert(isFloatReg(rs1));
            assert(isFloatReg(rs2));
            break;
        case INS_feq_s:
        case INS_feq_d:
        case INS_flt_d:
        case INS_flt_s:
        case INS_fle_s:
        case INS_fle_d:
            assert(isGeneralRegisterOrR0(rd));
            assert(isFloatReg(rs1));
            assert(isFloatReg(rs2));
            break;
        case INS_fmv_w_x:
        case INS_fmv_d_x:
            assert(isFloatReg(rd));
            assert(isGeneralRegisterOrR0(rs1));
            assert(rs2 == 0);
            break;
        case INS_fmv_x_d:
        case INS_fmv_x_w:
        case INS_fclass_s:
        case INS_fclass_d:
            assert(isGeneralRegisterOrR0(rd));
            assert(isFloatReg(rs1));
            assert(rs2 == 0);
            break;
        default:
            NO_WAY("Illegal ins within emitOutput_RTypeInstr!");
            break;
    }
}

/*static*/ void emitter::emitOutput_ITypeInstr_SanityCheck(
    instruction ins, regNumber rd, regNumber rs1, unsigned immediate, unsigned opcode)
{
    switch (ins)
    {
        case INS_mov:
        case INS_jalr:
        case INS_lb:
        case INS_lh:
        case INS_lw:
        case INS_lbu:
        case INS_lhu:
        case INS_addi:
        case INS_slti:
        case INS_sltiu:
        case INS_xori:
        case INS_ori:
        case INS_andi:
        case INS_lwu:
        case INS_ld:
        case INS_addiw:
        case INS_csrrw:
        case INS_csrrs:
        case INS_csrrc:
            assert(isGeneralRegisterOrR0(rd));
            assert(isGeneralRegisterOrR0(rs1));
            assert((opcode & kInstructionFunct7Mask) == 0);
            break;
        case INS_flw:
        case INS_fld:
            assert(isFloatReg(rd));
            assert(isGeneralRegisterOrR0(rs1));
            assert((opcode & kInstructionFunct7Mask) == 0);
            break;
        case INS_slli:
        case INS_srli:
        case INS_srai:
            assert(immediate < 64);
            assert(isGeneralRegisterOrR0(rd));
            assert(isGeneralRegisterOrR0(rs1));
            break;
        case INS_slliw:
        case INS_srliw:
        case INS_sraiw:
            assert(immediate < 32);
            assert(isGeneralRegisterOrR0(rd));
            assert(isGeneralRegisterOrR0(rs1));
            break;
        case INS_csrrwi:
        case INS_csrrsi:
        case INS_csrrci:
            assert(isGeneralRegisterOrR0(rd));
            assert(rs1 < 32);
            assert((opcode & kInstructionFunct7Mask) == 0);
            break;
        case INS_fence:
        {
            assert(rd == REG_ZERO);
            assert(rs1 == REG_ZERO);
            ssize_t format = immediate >> 8;
            assert((format == 0) || (format == 0x8));
            assert((opcode & kInstructionFunct7Mask) == 0);
        }
        break;
        default:
            NO_WAY("Illegal ins within emitOutput_ITypeInstr!");
            break;
    }
}

/*static*/ void emitter::emitOutput_STypeInstr_SanityCheck(instruction ins, regNumber rs1, regNumber rs2)
{
    switch (ins)
    {
        case INS_sb:
        case INS_sh:
        case INS_sw:
        case INS_sd:
            assert(isGeneralRegister(rs1));
            assert(isGeneralRegisterOrR0(rs2));
            break;
        case INS_fsw:
        case INS_fsd:
            assert(isGeneralRegister(rs1));
            assert(isFloatReg(rs2));
            break;
        default:
            NO_WAY("Illegal ins within emitOutput_STypeInstr!");
            break;
    }
}

/*static*/ void emitter::emitOutput_UTypeInstr_SanityCheck(instruction ins, regNumber rd)
{
    switch (ins)
    {
        case INS_lui:
        case INS_auipc:
            assert(isGeneralRegisterOrR0(rd));
            break;
        default:
            NO_WAY("Illegal ins within emitOutput_UTypeInstr!");
            break;
    }
}

/*static*/ void emitter::emitOutput_BTypeInstr_SanityCheck(instruction ins, regNumber rs1, regNumber rs2)
{
    switch (ins)
    {
        case INS_beqz:
        case INS_bnez:
            assert((rs1 == REG_ZERO) || (rs2 == REG_ZERO));
            FALLTHROUGH;
        case INS_beq:
        case INS_bne:
        case INS_blt:
        case INS_bge:
        case INS_bltu:
        case INS_bgeu:
            assert(isGeneralRegisterOrR0(rs1));
            assert(isGeneralRegisterOrR0(rs2));
            break;
        default:
            NO_WAY("Illegal ins within emitOutput_BTypeInstr!");
            break;
    }
}

/*static*/ void emitter::emitOutput_JTypeInstr_SanityCheck(instruction ins, regNumber rd)
{
    switch (ins)
    {
        case INS_j:
            assert(rd == REG_ZERO);
            break;
        case INS_jal:
            assert(isGeneralRegisterOrR0(rd));
            break;
        default:
            NO_WAY("Illegal ins within emitOutput_JTypeInstr!");
            break;
    }
}

#endif // DEBUG

/*****************************************************************************
 *
 *  Casts an integral or float register from their identification number to
 *  theirs binary format. In case of the integral registers the encoded number
 *  is the register id. In case of the floating point registers the encoded
 *  number is shifted back by the floating point register base (32) (The
 *  instruction itself specifies whether the register contains floating
 *  point or integer, in their encoding they are indistinguishable)
 *
 */

/*static*/ unsigned emitter::castFloatOrIntegralReg(regNumber reg)
{
    static constexpr unsigned kRegisterMask = 0x1f;

    assert(isGeneralRegisterOrR0(reg) || isFloatReg(reg));

    return reg & kRegisterMask;
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 R-Type instruction to the given buffer. Returns a
 *  length of an encoded instruction opcode
 *
 */

unsigned emitter::emitOutput_RTypeInstr(BYTE* dst, instruction ins, regNumber rd, regNumber rs1, regNumber rs2) const
{
    unsigned insCode = emitInsCode(ins);
#ifdef DEBUG
    emitOutput_RTypeInstr_SanityCheck(ins, rd, rs1, rs2);
#endif // DEBUG
    unsigned opcode = insCode & kInstructionOpcodeMask;
    unsigned funct3 = (insCode & kInstructionFunct3Mask) >> 12;
    unsigned funct7 = (insCode & kInstructionFunct7Mask) >> 25;
    return emitOutput_Instr(dst, insEncodeRTypeInstr(opcode, castFloatOrIntegralReg(rd), funct3,
                                                     castFloatOrIntegralReg(rs1), castFloatOrIntegralReg(rs2), funct7));
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 I-Type instruction to the given buffer. Returns a
 *  length of an encoded instruction opcode
 *
 */

unsigned emitter::emitOutput_ITypeInstr(BYTE* dst, instruction ins, regNumber rd, regNumber rs1, unsigned imm12) const
{
    unsigned insCode = emitInsCode(ins);
#ifdef DEBUG
    emitOutput_ITypeInstr_SanityCheck(ins, rd, rs1, imm12, insCode);
#endif // DEBUG
    unsigned opcode = insCode & kInstructionOpcodeMask;
    unsigned funct3 = (insCode & kInstructionFunct3Mask) >> 12;
    unsigned funct7 = (insCode & kInstructionFunct7Mask) >> 20; // only used by some of the immediate shifts
    return emitOutput_Instr(dst, insEncodeITypeInstr(opcode, castFloatOrIntegralReg(rd), funct3, rs1, imm12 | funct7));
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 S-Type instruction to the given buffer. Returns a
 *  length of an encoded instruction opcode
 *
 */

unsigned emitter::emitOutput_STypeInstr(BYTE* dst, instruction ins, regNumber rs1, regNumber rs2, unsigned imm12) const
{
    unsigned insCode = emitInsCode(ins);
#ifdef DEBUG
    emitOutput_STypeInstr_SanityCheck(ins, rs1, rs2);
#endif // DEBUG
    unsigned opcode = insCode & kInstructionOpcodeMask;
    unsigned funct3 = (insCode & kInstructionFunct3Mask) >> 12;
    return emitOutput_Instr(dst, insEncodeSTypeInstr(opcode, funct3, rs1, castFloatOrIntegralReg(rs2), imm12));
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 U-Type instruction to the given buffer. Returns a
 *  length of an encoded instruction opcode
 *
 */

unsigned emitter::emitOutput_UTypeInstr(BYTE* dst, instruction ins, regNumber rd, unsigned imm20) const
{
    unsigned insCode = emitInsCode(ins);
#ifdef DEBUG
    emitOutput_UTypeInstr_SanityCheck(ins, rd);
#endif // DEBUG
    return emitOutput_Instr(dst, insEncodeUTypeInstr(insCode, rd, imm20));
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 B-Type instruction to the given buffer. Returns a
 *  length of an encoded instruction opcode
 *
 */

unsigned emitter::emitOutput_BTypeInstr(BYTE* dst, instruction ins, regNumber rs1, regNumber rs2, unsigned imm13) const
{
    unsigned insCode = emitInsCode(ins);
#ifdef DEBUG
    emitOutput_BTypeInstr_SanityCheck(ins, rs1, rs2);
#endif // DEBUG
    unsigned opcode = insCode & kInstructionOpcodeMask;
    unsigned funct3 = (insCode & kInstructionFunct3Mask) >> 12;
    return emitOutput_Instr(dst, insEncodeBTypeInstr(opcode, funct3, rs1, rs2, imm13));
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 B-Type instruction with inverted comparation to
 *  the given buffer. Returns a length of an encoded instruction opcode
 *
 *  Note: Replaces:
 *      - beqz with bnez and vice versa
 *      - beq with bne and vice versa
 *      - blt with bge and vice versa
 *      - bltu with bgeu and vice versa
 */

unsigned emitter::emitOutput_BTypeInstr_InvertComparation(
    BYTE* dst, instruction ins, regNumber rs1, regNumber rs2, unsigned imm13) const
{
    unsigned insCode = emitInsCode(ins) ^ 0x1000;
#ifdef DEBUG
    emitOutput_BTypeInstr_SanityCheck(ins, rs1, rs2);
#endif // DEBUG
    unsigned opcode = insCode & kInstructionOpcodeMask;
    unsigned funct3 = (insCode & kInstructionFunct3Mask) >> 12;
    return emitOutput_Instr(dst, insEncodeBTypeInstr(opcode, funct3, rs1, rs2, imm13));
}

/*****************************************************************************
 *
 *  Emit a 32-bit RISCV64 J-Type instruction to the given buffer. Returns a
 *  length of an encoded instruction opcode
 *
 */

unsigned emitter::emitOutput_JTypeInstr(BYTE* dst, instruction ins, regNumber rd, unsigned imm21) const
{
    unsigned insCode = emitInsCode(ins);
#ifdef DEBUG
    emitOutput_JTypeInstr_SanityCheck(ins, rd);
#endif // JTypeInstructionSanityCheck
    return emitOutput_Instr(dst, insEncodeJTypeInstr(insCode, rd, imm21));
}

void emitter::emitOutputInstrJumpDistanceHelper(const insGroup* ig,
                                                instrDescJmp*   jmp,
                                                UNATIVE_OFFSET& dstOffs,
                                                const BYTE*&    dstAddr) const
{
    if (jmp->idAddr()->iiaHasInstrCount())
    {
        assert(ig != nullptr);
        int      instrCount = jmp->idAddr()->iiaGetInstrCount();
        unsigned insNum     = emitFindInsNum(ig, jmp);
        if (instrCount < 0)
        {
            // Backward branches using instruction count must be within the same instruction group.
            assert(insNum + 1 >= static_cast<unsigned>(-instrCount));
        }
        dstOffs = ig->igOffs + emitFindOffset(ig, insNum + 1 + instrCount);
        dstAddr = emitOffsetToPtr(dstOffs);
        return;
    }
    dstOffs = jmp->idAddr()->iiaIGlabel->igOffs;
    dstAddr = emitOffsetToPtr(dstOffs);
}

/*****************************************************************************
 *
 *  Calculates a current jump instruction distance
 *
 */

ssize_t emitter::emitOutputInstrJumpDistance(const BYTE* src, const insGroup* ig, instrDescJmp* jmp)
{
    UNATIVE_OFFSET srcOffs = emitCurCodeOffs(src);
    const BYTE*    srcAddr = emitOffsetToPtr(srcOffs);

    assert(!jmp->idAddr()->iiaIsJitDataOffset()); // not used by riscv64 impl

    UNATIVE_OFFSET dstOffs{};
    const BYTE*    dstAddr = nullptr;
    emitOutputInstrJumpDistanceHelper(ig, jmp, dstOffs, dstAddr);

    ssize_t distVal = static_cast<ssize_t>(dstAddr - srcAddr);

    if (dstOffs > srcOffs)
    {
        // This is a forward jump

        emitFwdJumps = true;

        // The target offset will be closer by at least 'emitOffsAdj', but only if this
        // jump doesn't cross the hot-cold boundary.
        if (!emitJumpCrossHotColdBoundary(srcOffs, dstOffs))
        {
            distVal -= emitOffsAdj;
            dstOffs -= emitOffsAdj;
        }
        jmp->idjOffs = dstOffs;
        if (jmp->idjOffs != dstOffs)
        {
            IMPL_LIMITATION("Method is too large");
        }
    }
    return distVal;
}

static inline constexpr unsigned WordMask(uint8_t bits)
{
    return static_cast<unsigned>((1ull << bits) - 1);
}

template <uint8_t MaskSize>
static unsigned LowerNBitsOfWord(ssize_t word)
{
    static_assert(MaskSize < 32, "Given mask size is bigger than the word itself");
    static_assert(MaskSize > 0, "Given mask size cannot be zero");

    static constexpr unsigned kMask = WordMask(MaskSize);

    return static_cast<unsigned>(word & kMask);
}

template <uint8_t MaskSize>
static unsigned UpperNBitsOfWord(ssize_t word)
{
    static constexpr unsigned kShift = 32 - MaskSize;

    return LowerNBitsOfWord<MaskSize>(word >> kShift);
}

template <uint8_t MaskSize>
static unsigned UpperNBitsOfWordSignExtend(ssize_t word)
{
    static constexpr unsigned kSignExtend = 1 << (31 - MaskSize);

    return UpperNBitsOfWord<MaskSize>(word + kSignExtend);
}

static unsigned UpperWordOfDoubleWord(ssize_t immediate)
{
    return static_cast<unsigned>(immediate >> 32);
}

static unsigned LowerWordOfDoubleWord(ssize_t immediate)
{
    static constexpr size_t kWordMask = WordMask(32);

    return static_cast<unsigned>(immediate & kWordMask);
}

template <uint8_t UpperMaskSize, uint8_t LowerMaskSize>
static ssize_t DoubleWordSignExtend(ssize_t doubleWord)
{
    static constexpr size_t kLowerSignExtend = static_cast<size_t>(1) << (63 - LowerMaskSize);
    static constexpr size_t kUpperSignExtend = static_cast<size_t>(1) << (63 - UpperMaskSize);

    return doubleWord + (kLowerSignExtend | kUpperSignExtend);
}

template <uint8_t UpperMaskSize>
static ssize_t UpperWordOfDoubleWordSingleSignExtend(ssize_t doubleWord)
{
    static constexpr size_t kUpperSignExtend = static_cast<size_t>(1) << (31 - UpperMaskSize);

    return UpperWordOfDoubleWord(doubleWord + kUpperSignExtend);
}

template <uint8_t UpperMaskSize, uint8_t LowerMaskSize>
static ssize_t UpperWordOfDoubleWordDoubleSignExtend(ssize_t doubleWord)
{
    return UpperWordOfDoubleWord(DoubleWordSignExtend<UpperMaskSize, LowerMaskSize>(doubleWord));
}

/*static*/ unsigned emitter::TrimSignedToImm12(ssize_t imm12)
{
    assert(isValidSimm12(imm12));

    return static_cast<unsigned>(LowerNBitsOfWord<12>(imm12));
}

/*static*/ unsigned emitter::TrimSignedToImm13(ssize_t imm13)
{
    assert(isValidSimm13(imm13));

    return static_cast<unsigned>(LowerNBitsOfWord<13>(imm13));
}

/*static*/ unsigned emitter::TrimSignedToImm20(ssize_t imm20)
{
    assert(isValidSimm20(imm20));

    return static_cast<unsigned>(LowerNBitsOfWord<20>(imm20));
}

/*static*/ unsigned emitter::TrimSignedToImm21(ssize_t imm21)
{
    assert(isValidSimm21(imm21));

    return static_cast<unsigned>(LowerNBitsOfWord<21>(imm21));
}

BYTE* emitter::emitOutputInstr_OptsReloc(BYTE* dst, const instrDesc* id, instruction* ins)
{
    BYTE* const dstBase = dst;

    regNumber dataReg = id->idReg1();
    regNumber addrReg = id->idReg2();

    *ins = id->idIns();
    assert(*ins == INS_addi || emitInsIsLoadOrStore(*ins));
    dst += emitOutput_UTypeInstr(dst, INS_auipc, addrReg, 0);
    emitGCregDeadUpd(addrReg, dst);
    dst += emitInsIsStore(*ins) ? emitOutput_STypeInstr(dst, *ins, addrReg, dataReg, 0)
                                : emitOutput_ITypeInstr(dst, *ins, dataReg, addrReg, 0);

    CorInfoReloc type = emitInsIsStore(*ins) ? CorInfoReloc::RISCV64_PCREL_S : CorInfoReloc::RISCV64_PCREL_I;
    emitRecordRelocation(dstBase, id->idAddr()->iiaAddr, type);
    return dst;
}

BYTE* emitter::emitOutputInstr_OptsRc(BYTE* dst, const instrDesc* id, instruction* ins)
{
    assert(id->idAddr()->iiaIsJitDataOffset());
    assert(id->idGCref() == GCT_NONE);

    const int offset = id->idAddr()->iiaGetJitDataOffset();
    assert(offset >= 0);
    assert((UNATIVE_OFFSET)offset < emitDataSize());

    *ins                 = id->idIns();
    const regNumber reg1 = id->idReg1();
    assert(reg1 != REG_ZERO);
    assert(id->idCodeSize() == 2 * sizeof(code_t));
    const ssize_t immediate = (emitConsBlock - dst) + offset;
    assert((immediate > 0) && ((immediate & 0x01) == 0));
    assert(isValidSimm32(immediate));

    const regNumber tempReg = isFloatReg(reg1) ? codeGen->rsGetRsvdReg() : reg1;
    dst += emitOutput_UTypeInstr(dst, INS_auipc, tempReg, UpperNBitsOfWordSignExtend<20>(immediate));
    dst += emitOutput_ITypeInstr(dst, *ins, reg1, tempReg, LowerNBitsOfWord<12>(immediate));
    return dst;
}

BYTE* emitter::emitOutputInstr_OptsRl(BYTE* dst, instrDesc* id, instruction* ins)
{
    insGroup* targetInsGroup = static_cast<insGroup*>(emitCodeGetCookie(id->idAddr()->iiaBBlabel));
    id->idAddr()->iiaIGlabel = targetInsGroup;

    const regNumber reg1   = id->idReg1();
    const ssize_t   igOffs = targetInsGroup->igOffs;

    *ins = INS_auipc;

    const ssize_t immediate = (emitCodeBlock - dst) + igOffs;
    assert((immediate & 0x01) == 0);
    assert(isValidSimm32(immediate));
    dst += emitOutput_UTypeInstr(dst, INS_auipc, reg1, UpperNBitsOfWordSignExtend<20>(immediate));
    dst += emitOutput_ITypeInstr(dst, INS_addi, reg1, reg1, LowerNBitsOfWord<12>(immediate));
    return dst;
}

BYTE* emitter::emitOutputInstr_OptsJump(BYTE* dst, instrDescJmp* jmp, const insGroup* ig, instruction* ins)
{
    ssize_t immediate = emitOutputInstrJumpDistance(dst, ig, jmp);
    assert((immediate & 0x01) == 0);
    assert(emitIsUncondJump(jmp) || emitIsCmpJump(jmp));

    *ins = jmp->idIns();
    if (jmp->idjShort)
    {
        assert(jmp->idCodeSize() == sizeof(code_t));
        if (emitIsUncondJump(jmp))
        {
            dst += emitOutput_JTypeInstr(dst, *ins, jmp->idReg1(), TrimSignedToImm21(immediate));
        }
        else
        {
            dst += emitOutput_BTypeInstr(dst, *ins, jmp->idReg1(), jmp->idReg2(), TrimSignedToImm13(immediate));
        }
    }
    else // far jump
    {
        if (emitIsUncondJump(jmp))
        {
            assert(jmp->idCodeSize() == 2 * sizeof(code_t));
            assert(isValidSimm32(immediate));
            regNumber linkReg = jmp->idReg1();
            regNumber tempReg = (linkReg == REG_ZERO) ? codeGen->rsGetRsvdReg() : linkReg;
            dst += emitOutput_UTypeInstr(dst, INS_auipc, tempReg, UpperNBitsOfWordSignExtend<20>(immediate));
            dst += emitOutput_ITypeInstr(dst, INS_jalr, linkReg, tempReg, LowerNBitsOfWord<12>(immediate));
        }
        else // opposite branch + jump
        {
            assert(!jmp->idInsIs(INS_beqz, INS_bnez) || (jmp->idReg2() == REG_ZERO));
            dst += emitOutput_BTypeInstr_InvertComparation(dst, *ins, jmp->idReg1(), jmp->idReg2(), jmp->idCodeSize());
            immediate -= sizeof(code_t);
            if (jmp->idCodeSize() == 2 * sizeof(code_t))
            {
                dst += emitOutput_JTypeInstr(dst, INS_jal, REG_ZERO, TrimSignedToImm21(immediate));
            }
            else
            {
                assert(jmp->idCodeSize() == 3 * sizeof(code_t));
                assert(isValidSimm32(immediate));
                regNumber tempReg = codeGen->rsGetRsvdReg();
                dst += emitOutput_UTypeInstr(dst, INS_auipc, tempReg, UpperNBitsOfWordSignExtend<20>(immediate));
                dst += emitOutput_ITypeInstr(dst, INS_jalr, REG_ZERO, tempReg, LowerNBitsOfWord<12>(immediate));
            }
        }
    }
    return dst;
}

BYTE* emitter::emitOutputInstr_OptsC(BYTE* dst, instrDesc* id, const insGroup* ig, size_t* size)
{
    if (id->idIsLargeCall())
    {
        *size = sizeof(instrDescCGCA);
    }
    else
    {
        assert(!id->idIsLargeDsp());
        assert(!id->idIsLargeCns());
        *size = sizeof(instrDesc);
    }
    dst += emitOutputCall(ig, dst, id);
    return dst;
}

BYTE* emitter::emitOutputInstr_OptsI(BYTE* dst, instrDesc* id, instruction* lastIns)
{
    assert(id->idInsOpt() == INS_OPTS_I);

    instrDescLoadImm* idli   = static_cast<instrDescLoadImm*>(id);
    instruction*      ins    = idli->ins;
    int32_t*          values = idli->values;
    regNumber         reg    = idli->idReg1();

    assert((reg != REG_NA) && (reg != REG_R0));

    int numberOfInstructions = idli->idCodeSize() / sizeof(code_t);
    for (int i = 0; i < numberOfInstructions; i++)
    {
        if ((i == 0) && (ins[0] == INS_lui))
        {
            assert(isValidSimm20(values[i]));
            dst += emitOutput_UTypeInstr(dst, ins[i], reg, values[i] & 0xfffff);
        }
        else if ((i == 0) && ((ins[0] == INS_addiw) || (ins[0] == INS_addi)))
        {
            assert(isValidSimm12(values[i]) || ((ins[i] == INS_addiw) && isValidUimm12(values[i])));
            dst += emitOutput_ITypeInstr(dst, ins[i], reg, REG_R0, values[i] & 0xfff);
        }
        else if (i == 0)
        {
            assert(false && "First instruction must be lui / addiw / addi");
        }
        else if ((ins[i] == INS_addi) || (ins[i] == INS_addiw) || (ins[i] == INS_slli) || (ins[i] == INS_srli))
        {
            assert(isValidSimm12(values[i]) || ((ins[i] == INS_addiw) && isValidUimm12(values[i])));
            dst += emitOutput_ITypeInstr(dst, ins[i], reg, reg, values[i] & 0xfff);
        }
        else
        {
            assert(false && "Remaining instructions must be addi / addiw / slli / srli");
        }
    }

    *lastIns = ins[numberOfInstructions - 1];

    return dst;
}

/*****************************************************************************
 *
 *  Append the machine code corresponding to the given instruction descriptor
 *  to the code block at '*dp'; the base of the code block is 'bp', and 'ig'
 *  is the instruction group that contains the instruction. Updates '*dp' to
 *  point past the generated code, and returns the size of the instruction
 *  descriptor in bytes.
 */

size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp)
{
    BYTE*             dst  = *dp;
    BYTE*             dst2 = dst + 4;
    const BYTE* const odst = *dp;
    instruction       ins;
    size_t            sz = 0;

    static_assert(REG_NA == static_cast<int>(REG_NA), "REG_NA must fit in an int");

    insOpts insOp = id->idInsOpt();

    switch (insOp)
    {
        case INS_OPTS_RELOC:
            dst = emitOutputInstr_OptsReloc(dst, id, &ins);
            sz  = sizeof(instrDesc);
            break;
        case INS_OPTS_RC:
            dst = emitOutputInstr_OptsRc(dst, id, &ins);
            sz  = sizeof(instrDesc);
            break;
        case INS_OPTS_RL:
            dst = emitOutputInstr_OptsRl(dst, id, &ins);
            sz  = sizeof(instrDesc);
            break;
        case INS_OPTS_JUMP:
            dst = emitOutputInstr_OptsJump(dst, static_cast<instrDescJmp*>(id), ig, &ins);
            sz  = sizeof(instrDescJmp);
            break;
        case INS_OPTS_C:
            dst  = emitOutputInstr_OptsC(dst, id, ig, &sz);
            dst2 = dst;
            ins  = INS_nop;
            break;
        case INS_OPTS_I:
            dst = emitOutputInstr_OptsI(dst, id, &ins);
            sz  = sizeof(instrDescLoadImm);
            break;
        default: // case INS_OPTS_NONE:
            dst += emitOutput_Instr(dst, id->idAddr()->iiaGetInstrEncode());
            ins = id->idIns();
            sz  = sizeof(instrDesc);
            break;
    }

    // Determine if any registers now hold GC refs, or whether a register that was overwritten held a GC ref.
    // We assume here that "id->idGCref()" is not GC_NONE only if the instruction described by "id" writes a
    // GC ref to register "id->idReg1()".  (It may, apparently, also not be GC_NONE in other cases, such as
    // for stores, but we ignore those cases here.)
    if (emitInsMayWriteToGCReg(ins)) // True if "id->idIns()" writes to a register than can hold GC ref.
    {
        // We assume that "idReg1" is the primary destination register for all instructions
        if (id->idGCref() != GCT_NONE)
        {
            emitGCregLiveUpd(id->idGCref(), id->idReg1(), dst2);
        }
        else
        {
            emitGCregDeadUpd(id->idReg1(), dst2);
        }
    }

    // Now we determine if the instruction has written to a (local variable) stack location, and either written a GC
    // ref or overwritten one.
    if (emitInsWritesToLclVarStackLoc(id) /*|| emitInsWritesToLclVarStackLocPair(id)*/)
    {
        int      varNum = id->idAddr()->iiaLclVar.lvaVarNum();
        unsigned ofs    = AlignDown(id->idAddr()->iiaLclVar.lvaOffset(), TARGET_POINTER_SIZE);
        bool     FPbased;
        int      adr = emitComp->lvaFrameAddress(varNum, &FPbased);
        if (id->idGCref() != GCT_NONE)
        {
            emitGCvarLiveUpd(adr + ofs, varNum, id->idGCref(), dst2 DEBUG_ARG(varNum));
        }
        else
        {
            // If the type of the local is a gc ref type, update the liveness.
            var_types vt;
            if (varNum >= 0)
            {
                // "Regular" (non-spill-temp) local.
                vt = var_types(emitComp->lvaTable[varNum].lvType);
            }
            else
            {
                TempDsc* tmpDsc = codeGen->regSet.tmpFindNum(varNum);
                vt              = tmpDsc->tdTempType();
            }
            if (vt == TYP_REF || vt == TYP_BYREF)
                emitGCvarDeadUpd(adr + ofs, dst2 DEBUG_ARG(varNum));
        }
    }

#ifdef DEBUG
    /* Make sure we set the instruction descriptor size correctly */

    if (emitComp->opts.disAsm || emitComp->verbose)
    {
#if DUMP_GC_TABLES
        bool dspOffs = emitComp->opts.dspGCtbls;
#else  // !DUMP_GC_TABLES
        bool dspOffs = !emitComp->opts.disDiffable;
#endif // !DUMP_GC_TABLES
        emitDispIns(id, false, dspOffs, true, emitCurCodeOffs(odst), *dp, (dst - odst), ig);
    }

    if (emitComp->compDebugBreak)
    {
        // For example, set JitBreakEmitOutputInstr=a6 will break when this method is called for
        // emitting instruction a6, (i.e. IN00a6 in jitdump).
        if ((unsigned)JitConfig.JitBreakEmitOutputInstr() == id->idDebugOnlyInfo()->idNum)
        {
            assert(!"JitBreakEmitOutputInstr reached");
        }
    }

    // Output any delta in GC info.
    if (EMIT_GC_VERBOSE || emitComp->opts.disasmWithGC)
    {
        emitDispGCInfoDelta();
    }

#else  // !DEBUG
    if (emitComp->opts.disAsm)
    {
        emitDispIns(id, false, false, true, emitCurCodeOffs(odst), *dp, (dst - odst), ig);
    }
#endif // !DEBUG

    /* All instructions are expected to generate code */

    assert(*dp != dst);

    *dp = dst;

    return sz;
}

/*****************************************************************************/
/*****************************************************************************/

// clang-format off
static const char* const RegNames[] =
{
    #define REGDEF(name, rnum, mask, sname) sname,
    #include "register.h"
};
// clang-format on

bool emitter::emitDispBranchInstrType(unsigned opcode2, bool is_zero_reg, bool& print_second_reg) const
{
    print_second_reg = true;
    switch (opcode2)
    {
        case 0:
            printf(is_zero_reg ? "beqz" : "beq ");
            print_second_reg = !is_zero_reg;
            break;
        case 1:
            printf(is_zero_reg ? "bnez" : "bne ");
            print_second_reg = !is_zero_reg;
            break;
        case 4:
            printf("blt ");
            break;
        case 5:
            printf("bge ");
            break;
        case 6:
            printf("bltu");
            break;
        case 7:
            printf("bgeu");
            break;
        default:
            return false;
    }
    return true;
}

void emitter::emitDispBranchOffset(const instrDesc* id, const insGroup* ig) const
{
    int instrCount = id->idAddr()->iiaGetInstrCount();
    if (ig == nullptr)
    {
        printf("pc%+d instructions", instrCount);
        return;
    }
    unsigned insNum = emitFindInsNum(ig, id);

    if (ig->igInsCnt < insNum + 1 + instrCount)
    {
        // TODO-RISCV64-BUG: This should be a labeled offset but does not contain an iiaIGlabel
        printf("pc%+d instructions", instrCount);
        return;
    }

    UNATIVE_OFFSET srcOffs = ig->igOffs + emitFindOffset(ig, insNum + 1);
    UNATIVE_OFFSET dstOffs = ig->igOffs + emitFindOffset(ig, insNum + 1 + instrCount);
    ssize_t        relOffs = static_cast<ssize_t>(emitOffsetToPtr(dstOffs) - emitOffsetToPtr(srcOffs));
    printf("pc%+d (%d instructions)", static_cast<int>(relOffs), instrCount);
}

void emitter::emitDispBranchLabel(const instrDesc* id) const
{
    if (id->idIsBound())
    {
        return emitPrintLabel(id->idAddr()->iiaIGlabel);
    }
    printf("L_M%03u_", FMT_BB, emitComp->compMethodID, id->idAddr()->iiaBBlabel->bbNum);
}

bool emitter::emitDispBranch(
    unsigned opcode2, unsigned rs1, unsigned rs2, const instrDesc* id, const insGroup* ig) const
{
    bool print_second_reg = true;
    if (!emitDispBranchInstrType(opcode2, rs2 == REG_ZERO, print_second_reg))
    {
        return false;
    }
    printf("           %s, ", RegNames[rs1]);
    if (print_second_reg)
    {
        printf("%s, ", RegNames[rs2]);
    }
    assert(id != nullptr);
    if (id->idAddr()->iiaHasInstrCount())
    {
        // Branch is jumping to some non-labeled offset
        emitDispBranchOffset(id, ig);
    }
    else
    {
        // Branch is jumping to the labeled offset
        emitDispBranchLabel(id);
    }
    printf("\n");
    return true;
}

void emitter::emitDispIllegalInstruction(code_t instructionCode)
{
    printf("RISCV64 illegal instruction: 0x%08X\n", instructionCode);
    assert(!"RISCV64 illegal instruction");
}

void emitter::emitDispImmediate(ssize_t imm, bool newLine /*= true*/, unsigned regBase /*= REG_ZERO*/)
{
    if (emitComp->opts.disDiffable && (regBase != REG_FP) && (regBase != REG_SP))
    {
        printf("0xD1FFAB1E");
    }
    else
    {
        printf("%li", imm);
    }

    if (newLine)
        printf("\n");
}

//----------------------------------------------------------------------------------------
// Disassemble the given instruction.
// The `emitter::emitDispInsName` is focused on the most important for debugging.
// So it implemented as far as simply and independently which is very useful for
// porting easily to the release mode.
//
// Arguments:
//    code - The instruction's encoding.
//    addr - The address of the code.
//    doffs - Flag informing whether the instruction's offset should be displayed.
//    insOffset - The instruction's offset.
//    id   - The instrDesc of the code if needed.
//    ig   - The insGroup of the code if needed
//
// Note:
//    The length of the instruction's name include aligned space is 15.
//

void emitter::emitDispInsName(
    code_t code, const BYTE* addr, bool doffs, unsigned insOffset, const instrDesc* id, const insGroup* ig)
{
    static constexpr int kMaxInstructionLength = 14;

    const BYTE* insAdr = addr - writeableOffset;
    emitDispInsAddr(insAdr);
    emitDispInsOffs(insOffset, doffs);

    if (emitComp->opts.disCodeBytes && !emitComp->opts.disDiffable)
    {
        int nNibbles = Is32BitInstruction((WORD)code) ? 8 : 4;
        printf("  %-8.*X    ", nNibbles, code);
    }

    printf("      ");

    bool willPrintLoadImmValue = (id->idInsOpt() == INS_OPTS_I) && !emitComp->opts.disDiffable;

    switch (GetMajorOpcode(code))
    {
        case MajorOpcode::Lui:
        {
            const char* rd    = RegNames[(code >> 7) & 0x1f];
            int         imm20 = (code >> 12) & 0xfffff;
            if (imm20 & 0x80000)
            {
                imm20 |= 0xfff00000;
            }
            printf("lui            %s, ", rd);
            emitDispImmediate(imm20, !willPrintLoadImmValue);
            return;
        }
        case MajorOpcode::Auipc:
        {
            const char* rd    = RegNames[(code >> 7) & 0x1f];
            int         imm20 = (code >> 12) & 0xfffff;
            if (imm20 & 0x80000)
            {
                imm20 |= 0xfff00000;
            }
            printf("auipc          %s, ", rd);
            emitDispImmediate(imm20);
            return;
        }
        case MajorOpcode::OpImm:
        {
            unsigned opcode2      = (code >> 12) & 0x7;
            unsigned rd           = (code >> 7) & 0x1f;
            unsigned rs1          = (code >> 15) & 0x1f;
            int      imm12        = static_cast<int>(code) >> 20;
            bool     hasImmediate = true;
            int      printLength  = 0;

            switch (opcode2)
            {
                case 0x0: // ADDI & MV & NOP
                    if (code == emitInsCode(INS_nop))
                    {
                        printf("nop\n");
                        return;
                    }
                    else if (imm12 != 0)
                    {
                        printLength = printf("addi");
                    }
                    else
                    {
                        printLength  = printf("mv");
                        hasImmediate = false;
                    }
                    break;
                case 0x1:
                {
                    unsigned funct6 = (imm12 >> 6) & 0x3f;
                    unsigned shamt  = imm12 & 0x3f; // 6 BITS for SHAMT in RISCV6
                    switch (funct6)
                    {
                        case 0b011000:
                        {
                            static const char* names[] = {"clz", "ctz", "cpop", nullptr, "sext.b", "sext.h"};
                            // shift amount is treated as additional funct opcode
                            if (shamt >= ARRAY_SIZE(names) || shamt == 3)
                                return emitDispIllegalInstruction(code);

                            assert(names[shamt] != nullptr);
                            printLength  = printf("%s", names[shamt]);
                            hasImmediate = false;
                            break;
                        }
                        case 0b000000:
                            printLength = printf("slli");
                            imm12       = shamt;
                            break;

                        default:
                            return emitDispIllegalInstruction(code);
                    }
                }
                break;
                case 0x2: // SLTI
                    printLength = printf("slti");
                    break;
                case 0x3: // SLTIU
                    printLength = printf("sltiu");
                    break;
                case 0x4: // XORI
                    if (imm12 == -1)
                    {
                        printLength  = printf("not");
                        hasImmediate = false;
                    }
                    else
                    {
                        printLength = printf("xori");
                    }
                    break;
                case 0x5: // SRLI & SRAI
                {
                    unsigned funct6 = (imm12 >> 6) & 0x3f;
                    imm12 &= 0x3f; // 6BITS for SHAMT in RISCV64
                    switch (funct6)
                    {
                        case 0b000000:
                            printLength = printf("srli");
                            break;
                        case 0b010000:
                            printLength = printf("srai");
                            break;
                        case 0b011000:
                            printLength = printf("rori");
                            break;
                        case 0b011010:
                            if (imm12 != 0b111000) // shift amount is treated as additional funct opcode
                                return emitDispIllegalInstruction(code);

                            printLength  = printf("rev8");
                            hasImmediate = false;
                            break;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                }
                break;
                case 0x6: // ORI
                    printLength = printf("ori");
                    break;
                case 0x7: // ANDI
                    printLength = printf("andi");
                    break;
                default:
                    return emitDispIllegalInstruction(code);
            }
            assert(printLength > 0);
            int paddingLength = kMaxInstructionLength - printLength;

            printf("%*s %s, %s", paddingLength, "", RegNames[rd], RegNames[rs1]);
            if (hasImmediate)
            {
                printf(", ");
                if (opcode2 == 0x0) // ADDI
                {
                    emitDispImmediate(imm12, false, rs1);
                }
                else
                {
                    printf("%d", imm12);
                }
            }
            if (!willPrintLoadImmValue)
            {
                printf("\n");
            }

            return;
        }
        case MajorOpcode::OpImm32:
        {
            unsigned int opcode2 = (code >> 12) & 0x7;
            const char*  rd      = RegNames[(code >> 7) & 0x1f];
            const char*  rs1     = RegNames[(code >> 15) & 0x1f];
            int          imm12   = (((int)code) >> 20);
            switch (opcode2)
            {
                case 0x0: // ADDIW & SEXT.W
                    if (imm12 == 0)
                    {
                        printf("sext.w         %s, %s\n", rd, rs1);
                    }
                    else
                    {
                        printf("addiw          %s, %s, ", rd, rs1);
                        emitDispImmediate(imm12, !willPrintLoadImmValue);
                    }
                    return;
                case 0x1: // SLLIW, SLLI.UW, CLZW, CTZW, & CPOPW
                {
                    static constexpr unsigned kSlliwFunct7  = 0b0000000;
                    static constexpr unsigned kSlliUwFunct6 = 0b000010;

                    unsigned funct7 = (imm12 >> 5) & 0x7f;
                    unsigned funct6 = (imm12 >> 6) & 0x3f;
                    // SLLIW's instruction code's upper 7 bits have to be equal to zero
                    if (funct7 == kSlliwFunct7)
                    {
                        printf("slliw          %s, %s, %d\n", rd, rs1, imm12 & 0x1f); // 5 BITS for SHAMT in RISCV64
                    }
                    // SLLI.UW's instruction code's upper 6 bits have to be equal to 0b000010
                    else if (funct6 == kSlliUwFunct6)
                    {
                        printf("slli.uw        %s, %s, %d\n", rd, rs1, imm12 & 0x3f); // 6 BITS for SHAMT in RISCV64
                    }
                    else if (funct7 == 0b0110000)
                    {
                        static const char* names[] = {"clzw ", "ctzw ", "cpopw"};
                        // shift amount is treated as funct additional opcode bits
                        unsigned shamt = imm12 & 0x1f; // 5 BITS for SHAMT in RISCV64
                        if (shamt >= ARRAY_SIZE(names))
                            return emitDispIllegalInstruction(code);

                        printf("%s          %s, %s\n", names[shamt], rd, rs1);
                    }
                    else
                    {
                        emitDispIllegalInstruction(code);
                    }
                }
                    return;
                case 0x5: // SRLIW & SRAIW
                {
                    unsigned funct7 = (imm12 >> 5) & 0x7f;
                    imm12 &= 0x1f; // 5BITS for SHAMT in RISCV64
                    switch (funct7)
                    {
                        case 0b0000000:
                            printf("srliw          %s, %s, %d\n", rd, rs1, imm12);
                            return;
                        case 0b0100000:
                            printf("sraiw          %s, %s, %d\n", rd, rs1, imm12);
                            return;
                        case 0b0110000:
                            printf("roriw          %s, %s, %d\n", rd, rs1, imm12);
                            return;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                }
                    return;
                default:
                    return emitDispIllegalInstruction(code);
            }
        }
        case MajorOpcode::Op:
        {
            unsigned int opcode2 = (code >> 25) & 0x7f;
            unsigned int opcode3 = (code >> 12) & 0x7;
            const char*  rd      = RegNames[(code >> 7) & 0x1f];
            const char*  rs1     = RegNames[(code >> 15) & 0x1f];
            const char*  rs2     = RegNames[(code >> 20) & 0x1f];

            switch (opcode2)
            {
                case 0b0000000:
                    switch (opcode3)
                    {
                        case 0x0: // ADD
                            printf("add            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x1: // SLL
                            printf("sll            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x2: // SLT
                            printf("slt            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x3: // SLTU
                            printf("sltu           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x4: // XOR
                            printf("xor            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x5: // SRL
                            printf("srl            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x6: // OR
                            printf("or             %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x7: // AND
                            printf("and            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                    return;
                case 0b0100000:
                    switch (opcode3)
                    {
                        case 0x0: // SUB
                            printf("sub            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x4: // XNOR
                            printf("xnor           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x5: // SRA
                            printf("sra            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x6: // ORN
                            printf("orn            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x7: // ANDN
                            printf("andn           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                    return;
                case 0b0000001:
                    switch (opcode3)
                    {
                        case 0x0: // MUL
                            printf("mul            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x1: // MULH
                            printf("mulh           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x2: // MULHSU
                            printf("mulhsu         %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x3: // MULHU
                            printf("mulhu          %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x4: // DIV
                            printf("div            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x5: // DIVU
                            printf("divu           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x6: // REM
                            printf("rem            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x7: // REMU
                            printf("remu           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                    return;
                case 0b0010000:
                    switch (opcode3)
                    {
                        case 0x2: // SH1ADD
                            printf("sh1add         %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x4: // SH2ADD
                            printf("sh2add         %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x6: // SH3ADD
                            printf("sh3add         %s, %s, %s\n", rd, rs1, rs2);
                            return;
                    }
                    return;
                case 0b0110000:
                    switch (opcode3)
                    {
                        case 0b001:
                            printf("rol            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0b101:
                            printf("ror            %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                    return;
                case 0b0000101:
                {
                    if ((opcode3 >> 2) != 1) // clmul[h] unsupported
                        return emitDispIllegalInstruction(code);

                    static const char names[][5] = {"min ", "minu", "max ", "maxu"};
                    printf("%s           %s, %s, %s\n", names[opcode3 & 0b11], rd, rs1, rs2);
                    return;
                }
                default:
                    return emitDispIllegalInstruction(code);
            }
        }
        case MajorOpcode::Op32:
        {
            unsigned int opcode2 = (code >> 25) & 0x7f;
            unsigned int opcode3 = (code >> 12) & 0x7;
            unsigned int rs2Num  = (code >> 20) & 0x1f;
            const char*  rd      = RegNames[(code >> 7) & 0x1f];
            const char*  rs1     = RegNames[(code >> 15) & 0x1f];
            const char*  rs2     = RegNames[rs2Num];

            switch (opcode2)
            {
                case 0b0000000:
                    switch (opcode3)
                    {
                        case 0x0: // ADDW
                            printf("addw           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x1: // SLLW
                            printf("sllw           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x5: // SRLW
                            printf("srlw           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                    return;
                case 0b0100000:
                    switch (opcode3)
                    {
                        case 0x0: // SUBW
                            printf("subw           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x5: // SRAW
                            printf("sraw           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                    return;
                case 0b0000001:
                    switch (opcode3)
                    {
                        case 0x0: // MULW
                            printf("mulw           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x4: // DIVW
                            printf("divw           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x5: // DIVUW
                            printf("divuw          %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x6: // REMW
                            printf("remw           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x7: // REMUW
                            printf("remuw          %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                    return;
                case 0b0010000:
                    switch (opcode3)
                    {
                        case 0x2: // SH1ADD.UW
                            printf("sh1add.uw      %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x4: // SH2ADD.UW
                            printf("sh2add.uw      %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0x6: // SH3ADD.UW
                            printf("sh3add.uw      %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                    return;
                case 0b0110000:
                    switch (opcode3)
                    {
                        case 0b001:
                            printf("rolw           %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        case 0b101:
                            printf("rorw          %s, %s, %s\n", rd, rs1, rs2);
                            return;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                    return;
                case 0b0000100:
                    switch (opcode3)
                    {
                        case 0b000: // ZEXT.W & ADD.UW
                            if (rs2Num == REG_ZERO)
                            {
                                printf("zext.w         %s, %s\n", rd, rs1);
                            }
                            else
                            {
                                printf("add.uw         %s, %s, %s\n", rd, rs1, rs2);
                            }
                            return;
                        case 0b100: // ZEXT.H
                            // Note: zext.h is encoded as a pseudo for 'packw rd, rs1, zero' which is not in Zbb.
                            if (rs2Num != REG_ZERO)
                                return emitDispIllegalInstruction(code);

                            printf("zext.h         %s, %s\n", rd, rs1);
                            return;
                        default:
                            return emitDispIllegalInstruction(code);
                    }
                    return;

                default:
                    return emitDispIllegalInstruction(code);
            }
        }
        case MajorOpcode::Store:
        {
            unsigned int opcode2 = (code >> 12) & 0x7;
            if (opcode2 >= 4)
                return emitDispIllegalInstruction(code);

            unsigned    rs1Num = (code >> 15) & 0x1f;
            const char* rs1    = RegNames[rs1Num];
            const char* rs2    = RegNames[(code >> 20) & 0x1f];
            int         offset = (((code >> 25) & 0x7f) << 5) | ((code >> 7) & 0x1f);
            if (offset & 0x800)
            {
                offset |= 0xfffff000;
            }

            char width = "bhwd"[opcode2];
            printf("s%c             %s, ", width, rs2);
            emitDispImmediate(offset, false, rs1Num);
            printf("(%s)\n", rs1);
            return;
        }
        case MajorOpcode::Branch:
        {
            unsigned opcode2 = (code >> 12) & 0x7;
            unsigned rs1     = (code >> 15) & 0x1f;
            unsigned rs2     = (code >> 20) & 0x1f;
            if (!emitDispBranch(opcode2, rs1, rs2, id, ig))
            {
                emitDispIllegalInstruction(code);
            }
            return;
        }
        case MajorOpcode::Load:
        {
            unsigned int opcode2 = (code >> 12) & 0x7;
            unsigned     rs1Num  = (code >> 15) & 0x1f;
            const char*  rs1     = RegNames[rs1Num];
            const char*  rd      = RegNames[(code >> 7) & 0x1f];
            int          offset  = ((code >> 20) & 0xfff);
            if (offset & 0x800)
            {
                offset |= 0xfffff000;
            }

            char width  = "bhwd"[opcode2 & 0b011];
            char unsign = ((opcode2 & 0b100) != 0) ? 'u' : ' ';
            if (width == 'd' && unsign == 'u')
                return emitDispIllegalInstruction(code);

            printf("l%c%c            %s, ", width, unsign, rd);
            emitDispImmediate(offset, false, rs1Num);
            printf("(%s)\n", rs1);
            return;
        }
        case MajorOpcode::Jalr:
        {
            const unsigned rs1    = (code >> 15) & 0x1f;
            const unsigned rd     = (code >> 7) & 0x1f;
            int            offset = ((code >> 20) & 0xfff);
            if (offset & 0x800)
            {
                offset |= 0xfffff000;
            }

            if ((offset == 0) && (rs1 == REG_RA) && (rd == REG_ZERO))
            {
                printf("ret");
                return;
            }

            if ((offset == 0) && ((rd == REG_RA) || (rd == REG_ZERO)))
            {
                const char* name = (rd == REG_RA) ? "jalr" : "jr  ";
                printf("%s           %s", name, RegNames[rs1]);
            }
            else
            {
                printf("jalr           %s, ", RegNames[rd]);
                emitDispImmediate(offset, false);
                printf("(%s)", RegNames[rs1]);
            }
            CORINFO_METHOD_HANDLE handle = (CORINFO_METHOD_HANDLE)id->idDebugOnlyInfo()->idMemCookie;
            // Target for ret call is unclear, e.g.:
            //   jalr zero, 0(ra)
            // So, skip it
            if (handle != 0)
            {
                const char* methodName = emitComp->eeGetMethodFullName(handle);
                printf("\t\t// %s", methodName);
            }

            printf("\n");
            return;
        }
        case MajorOpcode::Jal:
        {
            unsigned rd = (code >> 7) & 0x1f;
            int offset  = (((code >> 31) & 0x1) << 20) | (((code >> 12) & 0xff) << 12) | (((code >> 20) & 0x1) << 11) |
                         (((code >> 21) & 0x3ff) << 1);
            if (offset & 0x80000)
            {
                offset |= 0xfff00000;
            }
            if ((rd == REG_ZERO) || (rd == REG_RA))
            {
                const char* name = (rd == REG_RA) ? "jal" : "j  ";
                printf("%s            ", name);

                if (id->idIsBound())
                {
                    emitPrintLabel(id->idAddr()->iiaIGlabel);
                }
                else
                {
                    printf("pc%+");
                    emitDispImmediate(offset / sizeof(code_t));
                    printf(" instructions");
                }
            }
            else
            {
                printf("jal            %s, ", RegNames[rd]);
                emitDispImmediate(offset, false);
            }
            CORINFO_METHOD_HANDLE handle = (CORINFO_METHOD_HANDLE)id->idDebugOnlyInfo()->idMemCookie;
            if (handle != 0)
            {
                const char* methodName = emitComp->eeGetMethodFullName(handle);
                printf("\t\t// %s", methodName);
            }

            printf("\n");
            return;
        }
        case MajorOpcode::MiscMem:
        {
            int pred = ((code) >> 24) & 0xf;
            int succ = ((code) >> 20) & 0xf;
            printf("fence          %d, %d\n", pred, succ);
            return;
        }
        case MajorOpcode::System:
        {
            unsigned int opcode2 = (code >> 12) & 0x7;
            if (opcode2 != 0)
            {
                const char* rd      = RegNames[(code >> 7) & 0x1f];
                int         csrtype = (code >> 20);
                if (opcode2 <= 0x3)
                {
                    const char* rs1 = RegNames[(code >> 15) & 0x1f];
                    switch (opcode2)
                    {
                        case 0x1: // CSRRW
                            printf("csrrw           %s, %d, %s\n", rd, csrtype, rs1);
                            return;
                        case 0x2: // CSRRS
                            printf("csrrs           %s, %d, %s\n", rd, csrtype, rs1);
                            return;
                        case 0x3: // CSRRC
                            printf("csrrc           %s, %d, %s\n", rd, csrtype, rs1);
                            return;
                        default:
                            printf("RISCV64 illegal instruction: 0x%08X\n", code);
                            break;
                    }
                }
                else
                {
                    unsigned imm5 = ((code >> 15) & 0x1f);
                    switch (opcode2)
                    {
                        case 0x5: // CSRRWI
                            printf("csrrwi           %s, %d, %d\n", rd, csrtype, imm5);
                            return;
                        case 0x6: // CSRRSI
                            printf("csrrsi           %s, %d, %d\n", rd, csrtype, imm5);
                            return;
                        case 0x7: // CSRRCI
                            printf("csrrci           %s, %d, %d\n", rd, csrtype, imm5);
                            return;
                        default:
                            printf("RISCV64 illegal instruction: 0x%08X\n", code);
                            break;
                    }
                }
            }

            if (code == emitInsCode(INS_ebreak))
            {
                printf("ebreak\n");
            }
            else if (code == emitInsCode(INS_ecall))
            {
                printf("ecall\n");
            }
            else
            {
                NYI_RISCV64("illegal ins within emitDisInsName!");
            }
            return;
        }
        case MajorOpcode::OpFp:
        {
            unsigned int opcode2 = (code >> 27) & 0x1f;
            unsigned int opType  = (code >> 25) & 0x3;
            unsigned int opcode3 = (code >> 20) & 0x1f;
            unsigned int opcode4 = (code >> 12) & 0x7;

            if (opType > 1)
                return emitDispIllegalInstruction(code); // half- and quad-precision FP instructions unsupported

            char type = "sdhq"[opType];

            const char* fd  = RegNames[((code >> 7) & 0x1f) | 0x20];
            const char* fs1 = RegNames[((code >> 15) & 0x1f) | 0x20];
            const char* fs2 = RegNames[((code >> 20) & 0x1f) | 0x20];

            const char* xd  = RegNames[(code >> 7) & 0x1f];
            const char* xs1 = RegNames[(code >> 15) & 0x1f];
            const char* xs2 = RegNames[(code >> 20) & 0x1f];

            switch (opcode2)
            {
                case 0b00000: // FADD
                    printf("fadd.%c         %s, %s, %s\n", type, fd, fs1, fs2);
                    return;
                case 0b00001: // FSUB
                    printf("fsub.%c         %s, %s, %s\n", type, fd, fs1, fs2);
                    return;
                case 0b00010: // FMUL
                    printf("fmul.%c         %s, %s, %s\n", type, fd, fs1, fs2);
                    return;
                case 0b00011: // FDIV
                    printf("fdiv.%c         %s, %s, %s\n", type, fd, fs1, fs2);
                    return;
                case 0b01011: // FSQRT
                    if (opcode3 == 0)
                    {
                        printf("fsqrt.%c        %s, %s\n", type, fd, fs1);
                    }
                    else
                    {
                        emitDispIllegalInstruction(code);
                    }
                    return;
                case 0b00100: // FSGNJ
                    NYI_IF(opcode4 >= 3, "RISC-V illegal fsgnj variant");
                    if (fs1 != fs2)
                    {
                        const char* variants[2][3] = {{".s ", "n.s", "x.s"}, {".d ", "n.d", "x.d"}};
                        printf("fsgnj%s        %s, %s, %s\n", variants[opType][opcode4], fd, fs1, fs2);
                    }
                    else // pseudos
                    {
                        const char* names[2][3] = {{"fmv.s ", "fneg.s", "fabs.s"}, {"fmv.d ", "fneg.d", "fabs.d"}};
                        printf("%s         %s, %s\n", names[opType][opcode4], fd, fs1);
                    }
                    return;
                case 0b00101: // FMIN & FMAX
                    if (opcode4 == 0)
                    {
                        printf("fmin.%c         %s, %s, %s\n", type, fd, fs1, fs2);
                    }
                    else if (opcode4 == 1)
                    {
                        printf("fmax.%c         %s, %s, %s\n", type, fd, fs1, fs2);
                    }
                    else
                    {
                        NYI_RISCV64("illegal ins within emitDisInsName!");
                    }
                    return;
                case 0b01000: // FCVT (float-float)
                    if (opcode3 > 1)
                        return emitDispIllegalInstruction(code); // half- and quad-precision FP instructions unsupported

                    printf("fcvt.%c.%c       %s, %s\n", type, "sdhq"[opcode3], fd, fs1);
                    return;
                case 0b11000: // FCVT (to int)
                    if (opcode3 == 0)
                    {
                        printf("fcvt.w.%c       %s, %s\n", type, xd, fs1);
                    }
                    else if (opcode3 == 1)
                    {
                        printf("fcvt.wu.%c      %s, %s\n", type, xd, fs1);
                    }
                    else if (opcode3 == 2)
                    {
                        printf("fcvt.l.%c       %s, %s\n", type, xd, fs1);
                    }
                    else if (opcode3 == 3)
                    {
                        printf("fcvt.lu.%c      %s, %s\n", type, xd, fs1);
                    }
                    else
                    {
                        NYI_RISCV64("illegal ins within emitDisInsName!");
                    }
                    return;
                case 0b11100: // FMV (to int) & FCLASS
                    if (opcode3 != 0)
                        emitDispIllegalInstruction(code);

                    if (opcode4 == 0)
                    {
                        type = (type == 's') ? 'w' : type;
                        printf("fmv.x.%c        %s, %s\n", type, xd, fs1);
                    }
                    else if (opcode4 == 1)
                    {
                        printf("fclass.%c       %s, %s\n", type, xd, fs1);
                    }
                    else
                    {
                        NYI_RISCV64("illegal ins within emitDisInsName!");
                    }
                    return;
                case 0b10100: // FCMP
                    if (opcode4 == 0)
                    {
                        printf("fle.%c          %s, %s, %s\n", type, xd, fs1, fs2);
                    }
                    else if (opcode4 == 1)
                    {
                        printf("flt.%c          %s, %s, %s\n", type, xd, fs1, fs2);
                    }
                    else if (opcode4 == 2)
                    {
                        printf("feq.%c          %s, %s, %s\n", type, xd, fs1, fs2);
                    }
                    else
                    {
                        NYI_RISCV64("illegal ins within emitDisInsName!");
                    }
                    return;
                case 0b11010: // FCVT (from int)
                    if (opcode3 == 0)
                    {
                        printf("fcvt.%c.w       %s, %s\n", type, fd, xs1);
                    }
                    else if (opcode3 == 1)
                    {
                        printf("fcvt.%c.wu      %s, %s\n", type, fd, xs1);
                    }
                    else if (opcode3 == 2)
                    {
                        printf("fcvt.%c.l       %s, %s\n", type, fd, xs1);
                    }
                    else if (opcode3 == 3)
                    {
                        printf("fcvt.%c.lu      %s, %s\n", type, fd, xs1);
                    }
                    else
                    {
                        NYI_RISCV64("illegal ins within emitDisInsName!");
                    }
                    return;
                case 0b11110: // FMV (from int)
                    if (opcode3 != 0 || opcode4 != 0)
                        emitDispIllegalInstruction(code);

                    type = (type == 's') ? 'w' : type;
                    printf("fmv.%c.x        %s, %s\n", type, fd, xs1);
                    return;
                default:
                    NYI_RISCV64("illegal ins within emitDisInsName!");
                    return;
            }
            return;
        }
        case MajorOpcode::StoreFp:
        {
            unsigned int opcode2 = (code >> 12) & 0x7;
            if ((opcode2 != 2) && (opcode2 != 3))
                return emitDispIllegalInstruction(code);

            unsigned    rs1Num = (code >> 15) & 0x1f;
            const char* rs1    = RegNames[rs1Num];
            const char* rs2    = RegNames[((code >> 20) & 0x1f) | 0x20];
            int         offset = (((code >> 25) & 0x7f) << 5) | ((code >> 7) & 0x1f);
            if (offset & 0x800)
            {
                offset |= 0xfffff000;
            }

            char width = "bhwd"[opcode2];
            printf("fs%c            %s, ", width, rs2);
            emitDispImmediate(offset, false, rs1Num);
            printf("(%s)\n", rs1);
            return;
        }
        case MajorOpcode::LoadFp:
        {
            unsigned int opcode2 = (code >> 12) & 0x7;
            if ((opcode2 != 2) && (opcode2 != 3))
                return emitDispIllegalInstruction(code);

            unsigned    rs1Num = (code >> 15) & 0x1f;
            const char* rs1    = RegNames[rs1Num];
            const char* rd     = RegNames[((code >> 7) & 0x1f) | 0x20];
            int         offset = ((code >> 20) & 0xfff);
            if (offset & 0x800)
            {
                offset |= 0xfffff000;
            }

            char width = "bhwd"[opcode2];
            printf("fl%c            %s, ", width, rd);
            emitDispImmediate(offset, false, rs1Num);
            printf("(%s)\n", rs1);
            return;
        }
        case MajorOpcode::Amo:
        {
            bool        hasDataReg = true;
            const char* name;
            switch (code >> 27) // funct5
            {
                case 0b00010:
                    name       = "lr";
                    hasDataReg = false;
                    break;
                case 0b00011:
                    name = "sc";
                    break;
                case 0b00001:
                    name = "amoswap";
                    break;
                case 0b00000:
                    name = "amoadd";
                    break;
                case 0b00100:
                    name = "amoxor";
                    break;
                case 0b01100:
                    name = "amoand";
                    break;
                case 0b01000:
                    name = "amoor";
                    break;
                case 0b10000:
                    name = "amomin";
                    break;
                case 0b10100:
                    name = "amomax";
                    break;
                case 0b11000:
                    name = "amominu";
                    break;
                case 0b11100:
                    name = "amomaxu";
                    break;
                default:
                    assert(!"Illegal funct5 within atomic memory operation, emitDisInsName");
                    name = "?";
            }

            char width;
            switch ((code >> 12) & 0x7) // funct3: width
            {
                case 0x2:
                    width = 'w';
                    break;
                case 0x3:
                    width = 'd';
                    break;
                default:
                    assert(!"Illegal width tag within atomic memory operation, emitDisInsName");
                    width = '?';
            }

            const char* aq = code & (1 << 25) ? "aq" : "";
            const char* rl = code & (1 << 26) ? "rl" : "";

            int len = printf("%s.%c.%s%s", name, width, aq, rl);
            if (len <= 0)
            {
                return;
            }
            static const int INS_LEN = 14;
            assert(len <= INS_LEN);

            const char* dest = RegNames[(code >> 7) & 0x1f];
            const char* addr = RegNames[(code >> 15) & 0x1f];

            int dataReg = (code >> 20) & 0x1f;
            if (hasDataReg)
            {
                const char* data = RegNames[dataReg];
                printf("%*s %s, %s, (%s)\n", INS_LEN - len, "", dest, data, addr);
            }
            else
            {
                assert(dataReg == REG_R0);
                printf("%*s %s, (%s)\n", INS_LEN - len, "", dest, addr);
            }
            return;
        }
        case MajorOpcode::JrJalrMvAdd:
        {
            unsigned funct4 = (code >> 12) & 0xf;
            unsigned rdRs1  = (code >> 7) & 0x1f;
            unsigned rs2    = (code >> 2) & 0x1f;
            if (funct4 == 0b1001 && rdRs1 != REG_R0 && rs2 != REG_R0)
            {
                if (emitComp->opts.disCodeBytes)
                {
                    printf("c.add          %s, %s\n", RegNames[rdRs1], RegNames[rs2]);
                }
                else
                {
                    printf("add            %s, %s, %s\n", RegNames[rdRs1], RegNames[rdRs1], RegNames[rs2]);
                }
            }
            else if (funct4 == 0b1000 && rdRs1 != REG_R0 && rs2 != REG_R0)
            {
                const char* name = emitComp->opts.disCodeBytes ? "c.mv" : "mv  ";
                printf("%s           %s, %s\n", name, RegNames[rdRs1], RegNames[rs2]);
            }
            else
            {
                return emitDispIllegalInstruction(code);
            }
            return;
        }
        case MajorOpcode::MiscAlu:
        {
            unsigned funct6 = (code >> 10) & 0x3f;
            unsigned funct2 = (code >> 5) & 0x3;
            unsigned rdRs1  = getRegNumberFromRvcReg(((code >> 7) & 0x7));
            unsigned rs2    = getRegNumberFromRvcReg(((code >> 2) & 0x7));

            const char* name = nullptr;
            if (funct6 == 0b100011 && funct2 == 0b00)
            {
                name = "sub ";
            }
            else if (funct6 == 0b100011 && funct2 == 0b01)
            {
                name = "xor ";
            }
            else if (funct6 == 0b100011 && funct2 == 0b10)
            {
                name = "or  ";
            }
            else if (funct6 == 0b100011 && funct2 == 0b11)
            {
                name = "and ";
            }
            else if (funct6 == 0b100111 && funct2 == 0b00)
            {
                name = "subw";
            }
            else if (funct6 == 0b100111 && funct2 == 0b01)
            {
                name = "addw";
            }
            else
            {
                emitDispIllegalInstruction(code);
            }

            if (emitComp->opts.disCodeBytes)
            {
                printf("c.%s         %s, %s\n", name, RegNames[rdRs1], RegNames[rs2]);
            }
            else
            {
                printf("%s           %s, %s, %s\n", name, RegNames[rdRs1], RegNames[rdRs1], RegNames[rs2]);
            }
            return;
        }
        default:
            NO_WAY("illegal ins within emitDisInsName!");
    }

    NO_WAY("illegal ins within emitDisInsName!");
}

void emitter::emitDispInsInstrNum(const instrDesc* id) const
{
#ifdef DEBUG
    if (!emitComp->verbose)
        return;

    printf("IN%04x: ", id->idDebugOnlyInfo()->idNum);
#endif // DEBUG
}

void emitter::emitDispIns(
    instrDesc* id, bool isNew, bool doffs, bool asmfm, unsigned offset, BYTE* pCode, size_t sz, insGroup* ig)
{
    if (pCode == nullptr)
        return;

    emitDispInsInstrNum(id);

    bool willPrintLoadImmValue = (id->idInsOpt() == INS_OPTS_I) && !emitComp->opts.disDiffable;

    const BYTE* instr = pCode + writeableOffset;
    unsigned    instrSize;
    for (size_t i = 0; i < sz; instr += instrSize, i += instrSize, offset += instrSize)
    {
        WORD word;
        memcpy(&word, instr, sizeof(word));
        code_t instruction = word;
        instrSize          = sizeof(word);
        if (Is32BitInstruction(word))
        {
            memcpy(&word, instr + sizeof(word), sizeof(word));
            instruction |= word << 16;
            instrSize = 4;
        }
#ifdef DEBUG
        if (emitComp->verbose && i != 0)
        {
            printf("        ");
        }
#endif
        emitDispInsName(instruction, instr, doffs, offset, id, ig);

        if (willPrintLoadImmValue && ((i + instrSize) < sz))
        {
            printf("\n");
        }
    }

    if (willPrintLoadImmValue)
    {
        instrDescLoadImm* liid = static_cast<instrDescLoadImm*>(id);
        printf("\t\t;; load imm: hex=0x%016lX dec=%ld\n", liid->idcCnsVal, liid->idcCnsVal);
    }
}

#ifdef DEBUG

/*****************************************************************************
 *
 *  Display a stack frame reference.
 */

void emitter::emitDispFrameRef(int varx, int disp, int offs, bool asmfm)
{
    NYI_RISCV64("emitDispFrameRef-----unimplemented/unused on RISCV64 yet----");
}

#endif // DEBUG

// Generate code for a load or store operation with a potentially complex addressing mode
// This method handles the case of a GT_IND with contained GT_LEA op1 of the form [base + offset]
//
void emitter::emitInsLoadStoreOp(instruction ins, emitAttr attr, regNumber dataReg, GenTreeIndir* indir)
{
    GenTree* addr = indir->Addr();

    if (addr->isContained())
    {
        assert(addr->OperIs(GT_LCL_ADDR, GT_LEA, GT_CNS_INT));
        assert(!addr->OperIs(GT_LEA) || (!addr->AsAddrMode()->HasIndex() && addr->AsAddrMode()->gtScale <= 1));

        ssize_t offset = indir->Offset();

        GenTree* memBase = indir->Base();

        if (addr->OperIs(GT_LCL_ADDR))
        {
            GenTreeLclVarCommon* varNode = addr->AsLclVarCommon();
            unsigned             lclNum  = varNode->GetLclNum();
            unsigned             lclOffs = varNode->GetLclOffs();
            if (emitInsIsStore(ins))
            {
                emitIns_S_R(ins, attr, dataReg, lclNum, lclOffs);
            }
            else
            {
                emitIns_R_S(ins, attr, dataReg, lclNum, lclOffs);
            }
        }
        else if (addr->OperIs(GT_CNS_INT))
        {
            assert(memBase == addr);
            assert(offset == addr->AsIntCon()->IconValue());
            if (!addr->AsIntCon()->AddrNeedsReloc(emitComp) && isValidSimm12(offset))
            {
                emitIns_R_R_I(ins, attr, dataReg, REG_ZERO, offset);
            }
            else
            {
                bool needTemp = indir->OperIs(GT_STOREIND, GT_NULLCHECK) || varTypeIsFloating(indir);
                if (addr->AsIntCon()->FitsInAddrBase(emitComp) && addr->AsIntCon()->AddrNeedsReloc(emitComp))
                {
                    regNumber addrReg = needTemp ? codeGen->internalRegisters.GetSingle(indir) : dataReg;
                    attr              = EA_SET_FLG(attr, EA_DSP_RELOC_FLG);
                    emitIns_R_AI(ins, attr, dataReg, addrReg, (size_t)offset, offset, addr->GetIconHandleFlag());
                }
                else
                {
                    ssize_t lo12 = (offset << (64 - 12)) >> (64 - 12); // low 12 bits, sign-extended
                    offset -= lo12;

                    regNumber addrReg = REG_ZERO;
                    if (offset != 0)
                    {
                        addrReg = needTemp ? codeGen->internalRegisters.GetSingle(indir) : dataReg;
                        emitLoadImmediate(EA_PTRSIZE, addrReg, offset);
                    }
                    emitIns_R_R_I(ins, attr, dataReg, addrReg, lo12);
                }
            }
        }
        else if (isValidSimm12(offset))
        {
            // Then load/store dataReg from/to [memBase + offset]
            emitIns_R_R_I(ins, attr, dataReg, memBase->GetRegNum(), offset);
        }
        else
        {
            ssize_t lo12 = (offset << (64 - 12)) >> (64 - 12);
            offset -= lo12;

            // We require a tmpReg to hold the offset
            regNumber tmpReg = codeGen->internalRegisters.GetSingle(indir);

            // First load/store tmpReg with the large offset constant
            emitLoadImmediate(EA_PTRSIZE, tmpReg, offset);

            // Then load/store dataReg from/to [memBase + tmpReg]
            emitAttr addType = varTypeIsGC(memBase) ? EA_BYREF : EA_PTRSIZE;
            emitIns_R_R_R(INS_add, addType, tmpReg, memBase->GetRegNum(), tmpReg);
            emitIns_R_R_I(ins, attr, dataReg, tmpReg, lo12);
        }
    }
    else // addr is not contained, so we evaluate it into a register
    {
#ifdef DEBUG
        if (addr->OperIs(GT_LCL_ADDR))
        {
            // If the local var is a gcref or byref, the local var better be untracked, because we have
            // no logic here to track local variable lifetime changes, like we do in the contained case
            // above. E.g., for a `st a0,[a1]` for byref `a1` to local `V01`, we won't store the local
            // `V01` and so the emitter can't update the GC lifetime for `V01` if this is a variable birth.
            LclVarDsc* varDsc = emitComp->lvaGetDesc(addr->AsLclVarCommon());
            assert(!varDsc->lvTracked);
        }
#endif // DEBUG

        // Then load/store dataReg from/to [addrReg]
        emitIns_R_R_I(ins, attr, dataReg, addr->GetRegNum(), 0);
    }
}

// The callee must call genConsumeReg() for any non-contained srcs
// and genProduceReg() for any non-contained dsts.

regNumber emitter::emitInsBinary(instruction ins, emitAttr attr, GenTree* dst, GenTree* src)
{
    NYI_RISCV64("emitInsBinary-----unimplemented/unused on RISCV64 yet----");
    return REG_R0;
}

// The callee must call genConsumeReg() for any non-contained srcs
// and genProduceReg() for any non-contained dsts.
regNumber emitter::emitInsTernary(instruction ins, emitAttr attr, GenTree* dst, GenTree* src1, GenTree* src2)
{
    // dst can only be a reg
    assert(!dst->isContained());

    // find immed (if any) - it cannot be a dst
    // Only one src can be an int.
    GenTreeIntConCommon* intConst  = nullptr;
    GenTree*             nonIntReg = nullptr;

    const bool needCheckOv = dst->gtOverflowEx();

    if (varTypeIsFloating(dst))
    {
        // src1 can only be a reg
        assert(!src1->isContained());
        // src2 can only be a reg
        assert(!src2->isContained());
    }
    else // not floating point
    {
        // src2 can be immed or reg
        assert(!src2->isContained() || src2->isContainedIntOrIImmed());

        // Check src2 first as we can always allow it to be a contained immediate
        if (src2->isContainedIntOrIImmed())
        {
            intConst  = src2->AsIntConCommon();
            nonIntReg = src1;
        }
        // Only for commutative operations do we check src1 and allow it to be a contained immediate
        else if (dst->OperIsCommutative())
        {
            // src1 can be immed or reg
            assert(!src1->isContained() || src1->isContainedIntOrIImmed());

            // Check src1 and allow it to be a contained immediate
            if (src1->isContainedIntOrIImmed())
            {
                assert(!src2->isContainedIntOrIImmed());
                intConst  = src1->AsIntConCommon();
                nonIntReg = src2;
            }
        }
        else
        {
            // src1 can only be a reg
            assert(!src1->isContained());
        }
    }

#ifdef DEBUG
    if (needCheckOv)
    {
        if (ins == INS_add)
        {
            assert(attr == EA_8BYTE);
        }
        else if (ins == INS_addw) // || ins == INS_add
        {
            assert(attr == EA_4BYTE);
        }
        else if (ins == INS_addi)
        {
            assert(intConst != nullptr);
        }
        else if (ins == INS_addiw)
        {
            assert(intConst != nullptr);
        }
        else if (ins == INS_sub)
        {
            assert(attr == EA_8BYTE);
        }
        else if (ins == INS_subw)
        {
            assert(attr == EA_4BYTE);
        }
        else if ((ins == INS_mul) || (ins == INS_mulh) || (ins == INS_mulhu))
        {
            assert(attr == EA_8BYTE);
            // NOTE: overflow format doesn't support an int constant operand directly.
            assert(intConst == nullptr);
        }
        else if (ins == INS_mulw)
        {
            assert(attr == EA_4BYTE);
            // NOTE: overflow format doesn't support an int constant operand directly.
            assert(intConst == nullptr);
        }
        else
        {
            printf("RISCV64-Invalid ins for overflow check: %s\n", codeGen->genInsName(ins));
            assert(!"Invalid ins for overflow check");
        }
    }
#endif // DEBUG

    regNumber dstReg  = dst->GetRegNum();
    regNumber src1Reg = src1->GetRegNum();
    regNumber src2Reg = src2->GetRegNum();

    if (intConst != nullptr)
    {
        ssize_t imm = intConst->IconValue();
        assert(isValidSimm12(imm));

        if (ins == INS_sub)
        {
            assert(attr == EA_8BYTE);
            assert(imm != -2048);
            ins = INS_addi;
            imm = -imm;
        }
        else if (ins == INS_subw)
        {
            assert(attr == EA_4BYTE);
            assert(imm != -2048);
            ins = INS_addiw;
            imm = -imm;
        }

        assert(ins == INS_addi || ins == INS_addiw || ins == INS_andi || ins == INS_ori || ins == INS_xori);

        regNumber tempReg = needCheckOv ? codeGen->internalRegisters.Extract(dst) : REG_NA;

        if (needCheckOv)
        {
            emitIns_R_R(INS_mov, attr, tempReg, nonIntReg->GetRegNum());
        }

        emitIns_R_R_I(ins, attr, dstReg, nonIntReg->GetRegNum(), imm);

        if (needCheckOv)
        {
            // At this point andi/ori/xori are excluded by previous checks
            assert(ins == INS_addi || ins == INS_addiw);

            // AS11 = B + C
            if ((dst->gtFlags & GTF_UNSIGNED) != 0)
            {
                codeGen->genJumpToThrowHlpBlk_la(SCK_OVERFLOW, INS_bltu, dstReg, nullptr, tempReg);
            }
            else
            {
                if (imm > 0)
                {
                    // B > 0 and C > 0, if A < B, goto overflow
                    BasicBlock* tmpLabel = codeGen->genCreateTempLabel();
                    emitIns_J_cond_la(INS_bge, tmpLabel, REG_R0, tempReg);
                    emitIns_R_R_I(INS_slti, EA_PTRSIZE, tempReg, dstReg, imm);

                    codeGen->genJumpToThrowHlpBlk_la(SCK_OVERFLOW, INS_bne, tempReg);

                    codeGen->genDefineTempLabel(tmpLabel);
                }
                else if (imm < 0)
                {
                    // B < 0 and C < 0, if A > B, goto overflow
                    BasicBlock* tmpLabel = codeGen->genCreateTempLabel();
                    emitIns_J_cond_la(INS_bge, tmpLabel, tempReg, REG_R0);
                    emitIns_R_R_I(INS_addi, attr, tempReg, REG_R0, imm);

                    codeGen->genJumpToThrowHlpBlk_la(SCK_OVERFLOW, INS_blt, tempReg, nullptr, dstReg);

                    codeGen->genDefineTempLabel(tmpLabel);
                }
            }
        }
    }
    else if (varTypeIsFloating(dst))
    {
        emitIns_R_R_R(ins, attr, dstReg, src1Reg, src2Reg);
    }
    else
    {
        regNumber tempReg = needCheckOv ? codeGen->internalRegisters.Extract(dst) : REG_NA;

        switch (dst->OperGet())
        {
            case GT_MUL:
            {
                if (!needCheckOv && !(dst->gtFlags & GTF_UNSIGNED))
                {
                    emitIns_R_R_R(ins, attr, dstReg, src1Reg, src2Reg);
                }
                else
                {
                    if (needCheckOv)
                    {
                        assert(tempReg != dstReg);
                        assert(tempReg != src1Reg);
                        assert(tempReg != src2Reg);

                        assert(REG_RA != dstReg);
                        assert(REG_RA != src1Reg);
                        assert(REG_RA != src2Reg);

                        if ((dst->gtFlags & GTF_UNSIGNED) != 0)
                        {
                            if (attr == EA_4BYTE)
                            {
                                emitIns_R_R_I(INS_slli, EA_8BYTE, tempReg, src1Reg, 32);
                                emitIns_R_R_I(INS_slli, EA_8BYTE, REG_RA, src2Reg, 32);
                                emitIns_R_R_R(INS_mulhu, EA_8BYTE, tempReg, tempReg, REG_RA);
                                emitIns_R_R_I(INS_srai, attr, tempReg, tempReg, 32);
                            }
                            else
                            {
                                emitIns_R_R_R(INS_mulhu, attr, tempReg, src1Reg, src2Reg);
                            }
                        }
                        else
                        {
                            if (attr == EA_4BYTE)
                            {
                                emitIns_R_R_R(INS_mul, EA_8BYTE, tempReg, src1Reg, src2Reg);
                                emitIns_R_R_I(INS_srai, attr, tempReg, tempReg, 32);
                            }
                            else
                            {
                                emitIns_R_R_R(INS_mulh, attr, tempReg, src1Reg, src2Reg);
                            }
                        }
                    }

                    // n * n bytes will store n bytes result
                    emitIns_R_R_R(ins, attr, dstReg, src1Reg, src2Reg);

                    if (needCheckOv)
                    {
                        assert(tempReg != dstReg);
                        assert(tempReg != src1Reg);
                        assert(tempReg != src2Reg);

                        if ((dst->gtFlags & GTF_UNSIGNED) != 0)
                        {
                            codeGen->genJumpToThrowHlpBlk_la(SCK_OVERFLOW, INS_bne, tempReg);
                        }
                        else
                        {
                            regNumber tempReg2 = codeGen->internalRegisters.Extract(dst);
                            assert(tempReg2 != dstReg);
                            assert(tempReg2 != src1Reg);
                            assert(tempReg2 != src2Reg);
                            size_t imm = (EA_SIZE(attr) == EA_8BYTE) ? 63 : 31;
                            emitIns_R_R_I(EA_SIZE(attr) == EA_8BYTE ? INS_srai : INS_sraiw, attr, tempReg2, dstReg,
                                          imm);
                            codeGen->genJumpToThrowHlpBlk_la(SCK_OVERFLOW, INS_bne, tempReg, nullptr, tempReg2);
                        }
                    }
                }
            }
            break;

            case GT_AND:
            case GT_AND_NOT:
            case GT_OR:
            case GT_OR_NOT:
            case GT_XOR:
            case GT_XOR_NOT:
            {
                emitIns_R_R_R(ins, attr, dstReg, src1Reg, src2Reg);

                // TODO-RISCV64-CQ: here sign-extend dst when deal with 32bit data is too conservative.
                if (EA_SIZE(attr) == EA_4BYTE)
                    emitIns_R_R(INS_sext_w, attr, dstReg, dstReg);
            }
            break;

            case GT_ADD:
            case GT_SUB:
            {
                regNumber regOp1       = src1Reg;
                regNumber regOp2       = src2Reg;
                regNumber saveOperReg1 = REG_NA;
                regNumber saveOperReg2 = REG_NA;

                if ((dst->gtFlags & GTF_UNSIGNED) && (attr == EA_8BYTE))
                {
                    if (src1->TypeIs(TYP_INT))
                    {
                        emitIns_R_R_I(INS_slli, EA_8BYTE, regOp1, regOp1, 32);
                        emitIns_R_R_I(INS_srli, EA_8BYTE, regOp1, regOp1, 32);
                    }
                    if (src2->TypeIs(TYP_INT))
                    {
                        emitIns_R_R_I(INS_slli, EA_8BYTE, regOp2, regOp2, 32);
                        emitIns_R_R_I(INS_srli, EA_8BYTE, regOp2, regOp2, 32);
                    }
                }

                if (needCheckOv)
                {
                    assert(!varTypeIsFloating(dst));

                    assert(tempReg != dstReg);

                    if (dstReg == regOp1)
                    {
                        assert(tempReg != regOp1);
                        saveOperReg1 = tempReg;
                        saveOperReg2 = (regOp1 == regOp2) ? tempReg : regOp2;
                        emitIns_R_R(INS_mov, attr, tempReg, regOp1);
                    }
                    else if (dstReg == regOp2)
                    {
                        assert(tempReg != regOp2);
                        saveOperReg1 = regOp1;
                        saveOperReg2 = tempReg;
                        emitIns_R_R(INS_mov, attr, tempReg, regOp2);
                    }
                    else
                    {
                        saveOperReg1 = regOp1;
                        saveOperReg2 = regOp2;
                    }
                }

                emitIns_R_R_R(ins, attr, dstReg, regOp1, regOp2);

                /*
                    Check if A = B + C
                    ADD : A = B + C
                    SUB : B = A - C
                    In case of addition:
                    dst = src1 + src2
                    A = dst
                    B = src1
                    C = src2
                    In case of subtraction:
                    dst = src1 - src2
                    src1 = dst + src2
                    A = src1
                    B = dst
                    C = src2
                */
                if (needCheckOv)
                {
                    regNumber resultReg = REG_NA;

                    if (dst->OperIs(GT_ADD))
                    {
                        resultReg = dstReg;
                        regOp1    = saveOperReg1;
                        regOp2    = saveOperReg2;
                    }
                    else
                    {
                        resultReg = saveOperReg1;
                        regOp1    = dstReg;
                        regOp2    = saveOperReg2;
                    }

                    instruction branchIns  = INS_none;
                    regNumber   branchReg1 = REG_NA;
                    regNumber   branchReg2 = REG_NA;

                    if ((dst->gtFlags & GTF_UNSIGNED) != 0)
                    {
                        // if A < B then overflow
                        branchIns  = INS_bltu;
                        branchReg1 = resultReg;
                        branchReg2 = regOp1;
                    }
                    else
                    {
                        regNumber tempReg1 = codeGen->internalRegisters.GetSingle(dst);

                        branchIns = INS_bne;

                        if (attr == EA_4BYTE)
                        {
                            assert(!src1->TypeIs(TYP_LONG));
                            assert(!src2->TypeIs(TYP_LONG));

                            emitIns_R_R_R(INS_add, attr, tempReg1, regOp1, regOp2);

                            // if 64-bit addition is not equal to 32-bit addition for 32-bit operands then overflow
                            branchReg1 = resultReg;
                            branchReg2 = tempReg1;
                        }
                        else
                        {
                            assert(attr == EA_8BYTE);
                            assert(tempReg != tempReg1);
                            // When the tempReg2 is being used then the tempReg has to be already dead
                            regNumber tempReg2 = tempReg;

                            emitIns_R_R_R(INS_slt, attr, tempReg1, resultReg, regOp1);
                            emitIns_R_R_I(INS_slti, attr, tempReg2, regOp2, 0);

                            // if ((A < B) != (C < 0)) then overflow
                            branchReg1 = tempReg1;
                            branchReg2 = tempReg2;
                        }
                    }

                    codeGen->genJumpToThrowHlpBlk_la(SCK_OVERFLOW, branchIns, branchReg1, nullptr, branchReg2);
                }
            }
            break;

            default:
                NO_WAY("unexpected instruction within emitInsTernary!");
        }
    }

    return dstReg;
}

unsigned emitter::get_curTotalCodeSize()
{
    return emitTotalCodeSize;
}

//----------------------------------------------------------------------------------------
// IsAddressInRange: Returns whether an address should be attempted to be reached with an auipc + load/store/jalr/addi
// instruction combo.
//
// Arguments:
//    addr - The address to check
//
// Return Value:
//    A struct containing the current instruction execution characteristics
bool emitter::IsAddressInRange(void* addr)
{
    return emitComp->opts.compReloc || (INDEBUG(emitComp->opts.compEnablePCRelAddr&&)(
                                           CorInfoReloc::RELATIVE32 == emitComp->eeGetRelocTypeHint(addr)));
}

#if defined(DEBUG) || defined(LATE_DISASM)

//----------------------------------------------------------------------------------------
// getInsExecutionCharacteristics:
//    Returns the current instruction execution characteristics based on the SiFive U74 core:
//    https://www.starfivetech.com/uploads/u74_core_complex_manual_21G1.pdf
//
// Arguments:
//    id  - The current instruction descriptor to be evaluated
//
// Return Value:
//    A struct containing the current instruction execution characteristics
//
// Notes:
//    The instruction latencies and throughput values returned by this function
//    are NOT accurate and just a function feature.
emitter::insExecutionCharacteristics emitter::getInsExecutionCharacteristics(instrDesc* id)
{
    insExecutionCharacteristics result;
    result.insThroughput       = PERFSCORE_LATENCY_1C;
    result.insLatency          = PERFSCORE_THROUGHPUT_1C;
    result.insMemoryAccessKind = PERFSCORE_MEMORY_NONE;

    unsigned codeSize = id->idCodeSize();
    assert((codeSize >= 2) && ((codeSize % 2) == 0));

    // Some instructions like jumps or loads may have not-yet-known simple auxilliary instructions (lui, addi, slli,
    // etc) for building immediates, assume cost of one each.
    // instrDescLoadImm consists of OpImm, OpImm32, and Lui instructions.
    float immediateBuildingCost = ((codeSize / sizeof(code_t)) - 1) * PERFSCORE_LATENCY_1C;

    instruction ins = id->idIns();
    assert(ins != INS_invalid);
    if ((ins == INS_lea) || (id->idInsOpt() == INS_OPTS_I))
    {
        result.insLatency += immediateBuildingCost;
        result.insThroughput += immediateBuildingCost;
        return result;
    }

    MajorOpcode opcode = GetMajorOpcode(emitInsCode(ins));
    switch (opcode)
    {
        case MajorOpcode::OpImm:
        case MajorOpcode::OpImm32:
        case MajorOpcode::Lui:
        case MajorOpcode::Auipc:
            result.insLatency    = PERFSCORE_LATENCY_1C;
            result.insThroughput = PERFSCORE_THROUGHPUT_2X;
            break;

        case MajorOpcode::Op:
        case MajorOpcode::Op32:
        case MajorOpcode::JrJalrMvAdd:
        case MajorOpcode::MiscAlu:
            if (id->idInsIs(INS_mul, INS_mulh, INS_mulhu, INS_mulhsu, INS_mulw))
            {
                result.insLatency = PERFSCORE_LATENCY_3C;
            }
            else if (id->idInsIs(INS_div, INS_divu, INS_rem, INS_remu))
            {
                result.insLatency = result.insThroughput = (6.0f + 68.0f) / 2;
            }
            else if (id->idInsIs(INS_divw, INS_divuw, INS_remw, INS_remuw))
            {
                result.insLatency = result.insThroughput = (6.0f + 36.0f) / 2;
            }
            else
            {
                result.insThroughput = PERFSCORE_THROUGHPUT_2X;
            }
            break;

        case MajorOpcode::MAdd:
        case MajorOpcode::MSub:
        case MajorOpcode::NmAdd:
        case MajorOpcode::NmSub:
        case MajorOpcode::OpFp:
            if (id->idInsIs(INS_fadd_s, INS_fsub_s, INS_fmul_s, INS_fmadd_s, INS_fmsub_s, INS_fnmadd_s, INS_fnmsub_s))
            {
                result.insLatency = PERFSCORE_LATENCY_5C;
            }
            else if (id->idInsIs(INS_fadd_d, INS_fsub_d, INS_fmul_d, INS_fmadd_d, INS_fmsub_d, INS_fnmadd_d,
                                 INS_fnmsub_d))
            {
                result.insLatency = PERFSCORE_LATENCY_7C;
            }
            else if (id->idInsIs(INS_fdiv_s))
            {
                result.insLatency    = (9.0f + 36.0f) / 2;
                result.insThroughput = (8.0f + 33.0f) / 2;
            }
            else if (id->idInsIs(INS_fsqrt_s))
            {
                result.insLatency    = (9.0f + 28.0f) / 2;
                result.insThroughput = (8.0f + 33.0f) / 2;
            }
            else if (id->idInsIs(INS_fdiv_d))
            {
                result.insLatency    = (9.0f + 58.0f) / 2;
                result.insThroughput = (8.0f + 58.0f) / 2;
            }
            else if (id->idInsIs(INS_fsqrt_d))
            {
                result.insLatency    = (9.0f + 57.0f) / 2;
                result.insThroughput = (8.0f + 58.0f) / 2;
            }
            else if (id->idInsIs(INS_feq_s, INS_fle_s, INS_flt_s, INS_fclass_s, INS_feq_d, INS_fle_d, INS_flt_d,
                                 INS_fclass_d, INS_fcvt_w_s, INS_fcvt_l_s, INS_fcvt_s_l, INS_fcvt_wu_s, INS_fcvt_lu_s,
                                 INS_fcvt_s_lu, INS_fcvt_w_d, INS_fcvt_l_d, INS_fcvt_wu_d, INS_fcvt_lu_d))
            {
                result.insLatency = PERFSCORE_LATENCY_4C;
            }
            else if (id->idInsIs(INS_fcvt_d_l, INS_fcvt_d_lu, INS_fmv_d_x))
            {
                result.insLatency = PERFSCORE_LATENCY_6C;
            }
            else if (id->idInsIs(INS_fmv_x_w, INS_fmv_x_d))
            {
                result.insLatency = PERFSCORE_LATENCY_1C;
            }
            else
            {
                result.insLatency = PERFSCORE_LATENCY_2C;
            }
            break;

        case MajorOpcode::Amo:
            result.insLatency = result.insThroughput = PERFSCORE_LATENCY_5C;
            result.insMemoryAccessKind               = PERFSCORE_MEMORY_READ_WRITE;
            break;

        case MajorOpcode::Branch:
            result.insLatency = result.insThroughput =
                immediateBuildingCost + (PERFSCORE_LATENCY_1C + PERFSCORE_LATENCY_6C) / 2;
            break;

        case MajorOpcode::Jalr:
            result.insLatency = result.insThroughput =
                immediateBuildingCost + (PERFSCORE_LATENCY_1C + PERFSCORE_LATENCY_5C) / 2;
            break;

        case MajorOpcode::Jal:
            result.insLatency = result.insThroughput =
                immediateBuildingCost + (PERFSCORE_LATENCY_1C + PERFSCORE_LATENCY_2C) / 2;
            break;

        case MajorOpcode::System:
        {
            code_t code   = id->idAddr()->iiaGetInstrEncode();
            code_t funct3 = (code >> 12) & 0b111;
            if (funct3 != 0)
            {
                bool isCsrrw      = ((funct3 & 0b11) == 0b01);
                bool isZero       = (((code >> 15) & 0b11111) == 0); // source register or 5-bit immediate is zero
                bool isWrite      = (isCsrrw || !isZero);
                result.insLatency = isWrite ? PERFSCORE_LATENCY_7C : PERFSCORE_LATENCY_1C;
            }
            break;
        }

        case MajorOpcode::Load:
        case MajorOpcode::Store:
        case MajorOpcode::LoadFp:
        case MajorOpcode::StoreFp:
        {
            bool isLoad = (opcode == MajorOpcode::Load || opcode == MajorOpcode::LoadFp);

            result.insLatency = isLoad ? PERFSCORE_LATENCY_2C : PERFSCORE_LATENCY_4C;
            if (isLoad)
            {
                code_t log2Size = (emitInsCode(ins) >> 12) & 0b11;
                if (log2Size < 2) // sub-word loads
                    result.insLatency += PERFSCORE_LATENCY_1C;
            }

            regNumber baseReg = id->idReg2();
            if (baseReg != REG_SP || baseReg != REG_FP)
                result.insLatency += PERFSCORE_LATENCY_1C; // assume non-stack load/stores are more likely to cache-miss

            result.insThroughput += immediateBuildingCost;
            result.insMemoryAccessKind = isLoad ? PERFSCORE_MEMORY_READ : PERFSCORE_MEMORY_WRITE;
            break;
        }

        case MajorOpcode::MiscMem:
            result.insLatency    = PERFSCORE_LATENCY_5C;
            result.insThroughput = PERFSCORE_THROUGHPUT_5C;
            break;

        default:
            perfScoreUnhandledInstruction(id, &result);
    }

    return result;
}

#endif // defined(DEBUG) || defined(LATE_DISASM)

#ifdef DEBUG
//------------------------------------------------------------------------
// emitRegName: Returns a general-purpose register name or SIMD and floating-point scalar register name.
//
// TODO-RISCV64: supporting SIMD.
// Arguments:
//    reg - A general-purpose register orfloating-point register.
//    size - unused parameter.
//    varName - unused parameter.
//
// Return value:
//    A string that represents a general-purpose register name or floating-point scalar register name.
//
const char* emitter::emitRegName(regNumber reg, emitAttr size, bool varName) const
{
    assert(reg < REG_COUNT);

    const char* rn = nullptr;

    rn = RegNames[reg];
    assert(rn != nullptr);

    return rn;
}
#endif

#endif // defined(TARGET_RISCV64)
